Failed Conditions
Pull Request — master (#229)
by Rafael
02:54
created

AuthController::refresh()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 0
dl 0
loc 27
ccs 0
cts 16
cp 0
crap 20
rs 9.488
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Canvas\Api\Controllers;
6
7
use Canvas\Models\Users;
8
use Canvas\Models\Sources;
9
use Canvas\Models\UserLinkedSources;
10
use Canvas\Exception\ServerErrorHttpException;
11
use Canvas\Exception\ModelException;
12
use Baka\Auth\Models\Users as BakaUsers;
13
use Canvas\Traits\AuthTrait;
14
use Canvas\Traits\SocialLoginTrait;
15
use Exception;
16
use Phalcon\Http\Response;
17
use Phalcon\Validation\Validator\Confirmation;
18
use Phalcon\Validation\Validator\Email as EmailValidator;
19
use Phalcon\Validation\Validator\PresenceOf;
20
use Phalcon\Validation\Validator\StringLength;
21
use Baka\Auth\Models\Sessions;
22
use Canvas\Auth\Factory;
23
use Canvas\Validation as CanvasValidation;
24
use Canvas\Notifications\ResetPassword;
25
use Canvas\Notifications\PasswordUpdate;
26
use Canvas\Validations\PasswordValidation;
27
use Canvas\Traits\TokenTrait;
28
29
/**
30
 * Class AuthController.
31
 *
32
 * @package Canvas\Api\Controllers
33
 *
34
 * @property Users $userData
35
 * @property Request $request
36
 * @property Config $config
37
 * @property \Baka\Mail\Message $mail
38
 * @property Apps $app
39
 */
40
class AuthController extends \Baka\Auth\AuthController
41
{
42
    /**
43
     * Auth Trait.
44
     */
45
    use AuthTrait;
46
    use TokenTrait;
47
    use SocialLoginTrait;
48
49
    /**
50
     * Setup for this controller.
51
     *
52
     * @return void
53
     */
54
    public function onConstruct()
55
    {
56
        $this->userLinkedSourcesModel = new UserLinkedSources();
57
        $this->userModel = new Users();
58
59
        if (!isset($this->config->jwt)) {
60
            throw new ServerErrorHttpException('You need to configure your app JWT');
0 ignored issues
show
Deprecated Code introduced by
The class Canvas\Exception\ServerErrorHttpException has been deprecated with message: version 0.1.5

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
61
        }
62
    }
63
64
    /**
65
     * User Login.
66
     * @method POST
67
     * @url /v1/auth
68
     *
69
     * @return Response
70
     */
71
    public function login() : Response
72
    {
73
        $request = $this->request->getPostData();
74
75
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
76
        $admin = 0;
77
        $remember = 1;
78
79
        //Ok let validate user password
80
        $validation = new CanvasValidation();
81
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid')]));
82
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
83
84
        $validation->setFilters('name', 'trim');
85
        $validation->setFilters('password', 'trim');
86
87
        //validate this form for password
88
        $validation->validate($request);
89
90
        $email = $validation->getValue('email');
91
        $password = $validation->getValue('password');
92
93
        /**
94
         * Login the user via ecosystem or app.
95
         */
96
        $auth = Factory::create($this->app->ecosystemAuth());
97
        $userData = $auth::login($email, $password, $remember, $admin, $userIp);
98
        $token = $userData->getToken();
99
100
        //start session
101
        $session = new Sessions();
102
        $session->start($userData, $token['sessionId'], $token['token'], $userIp, 1);
103
104
        return $this->response([
105
            'token' => $token['token'],
106
            'refresh_token' => $token['refresh_token'],
107
            'time' => date('Y-m-d H:i:s'),
108
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
109
            'refresh_token_expires' => date('Y-m-d H:i:s', time() + 31536000),
110
            'id' => $userData->getId()
111
        ]);
112
    }
113
114
    /**
115
     * User Signup.
116
     *
117
     * @method POST
118
     * @url /v1/users
119
     *
120
     * @return Response
121
     */
122
    public function signup() : Response
123
    {
124
        $user = $this->userModel;
125
126
        $request = $this->request->getPostData();
127
128
        //Ok let validate user password
129
        $validation = new CanvasValidation();
130
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
131
        $validation->add('firstname', new PresenceOf(['message' => _('The firstname is required.')]));
132
        $validation->add('lastname', new PresenceOf(['message' => _('The lastname is required.')]));
133
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
134
135
        $validation->add(
136
            'password',
137
            new StringLength([
138
                'min' => 8,
139
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
140
            ])
141
        );
142
143
        $validation->add('password', new Confirmation([
144
            'message' => _('Password and confirmation do not match.'),
145
            'with' => 'verify_password',
146
        ]));
147
148
        $validation->setFilters('password', 'trim');
149
        $validation->setFilters('displayname', 'trim');
150
        $validation->setFilters('default_company', 'trim');
151
152
        //validate this form for password
153
        $validation->validate($request);
154
155
        $user->email = $validation->getValue('email');
156
        $user->firstname = $validation->getValue('firstname');
157
        $user->lastname = $validation->getValue('lastname');
158
        $user->password = $validation->getValue('password');
159
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
160
        $user->displayname = $validation->getValue('displayname');
161
        $user->defaultCompanyName = $validation->getValue('default_company');
162
163
        //user registration
164
        try {
165
            $this->db->begin();
166
167
            $user->signUp();
168
169
            $this->db->commit();
170
        } catch (Exception $e) {
171
            $this->db->rollback();
172
173
            throw new Exception($e->getMessage());
174
        }
175
176
        $token = $user->getToken();
177
178
        //start session
179
        $session = new Sessions();
180
        $session->start($user, $token['sessionId'], $token['token'], $userIp, 1);
181
182
        $authSession = [
183
            'token' => $token['token'],
184
            'time' => date('Y-m-d H:i:s'),
185
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
186
            'id' => $user->getId(),
187
        ];
188
189
        $user->password = null;
190
        $this->sendEmail($user, 'signup');
191
192
        return $this->response([
193
            'user' => $user,
194
            'session' => $authSession
195
        ]);
196
    }
197
198
    /**
199
     * Refresh user auth.
200
     *
201
     * @return Response
202
     * @todo Validate acces_token and refresh token, session's user email and relogin
203
     */
204
    public function refresh(): Response
205
    {
206
        $request = $this->request->getPostData();
207
        $accessToken = $this->getToken($request['access_token']);
208
        $refreshToken = $this->getToken($request['refresh_token']);
209
210
        if (time() != $accessToken->getClaim('exp')) {
211
            throw new ServerErrorHttpException('Issued Access Token has not expired');
212
        }
213
214
        //Check if both tokens relate to the same user's email
215
        if ($accessToken->getClaim('sessionId') == $refreshToken->getClaim('sessionId')) {
216
            $user = Users::getByEmail($accessToken->getClaim('email'));
217
            if (!$user) {
218
                throw new NotFoundHttpException(_('User not found'));
219
            }
220
        }
221
222
        $token = Sessions::restart($user, $refreshToken->getClaim('sessionId'), (string)$this->request->getClientAddress());
0 ignored issues
show
Bug introduced by
The variable $user does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
223
224
        return $this->response([
225
            'token' => $token['token'],
226
            'time' => date('Y-m-d H:i:s'),
227
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
228
            'id' => $user->getId(),
229
        ]);
230
    }
231
232
    /**
233
     * Send email to change current email for user.
234
     * @param int $id
235
     * @return Response
236
     */
237
    public function sendEmailChange(int $id): Response
238
    {
239
        //Search for user
240
        $user = Users::getById($id);
241
242
        if (!is_object($user)) {
243
            throw new NotFoundHttpException(_('User not found'));
244
        }
245
246
        //Send email
247
        $this->sendEmail($user, 'email-change');
248
249
        return $this->response($user);
250
    }
251
252
    /**
253
     * Change user's email.
254
     * @param string $hash
255
     * @return Response
256
     */
257
    public function changeUserEmail(string $hash): Response
258
    {
259
        $request = $this->request->getPostData();
260
261
        //Ok let validate user password
262
        $validation = new CanvasValidation();
263
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
264
        $validation->add('new_email', new EmailValidator(['message' => _('The email is not valid.')]));
265
266
        $validation->add(
267
            'password',
268
            new StringLength([
269
                'min' => 8,
270
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
271
            ])
272
        );
273
274
        //validate this form for password
275
        $validation->setFilters('password', 'trim');
276
        $validation->setFilters('default_company', 'trim');
277
        $validation->validate($request);
278
279
        $newEmail = $validation->getValue('new_email');
280
        $password = $validation->getValue('password');
281
282
        //Search user by key
283
        $user = Users::getByUserActivationEmail($hash);
284
285
        if (!is_object($user)) {
286
            throw new NotFoundHttpException(_('User not found'));
287
        }
288
289
        $this->db->begin();
290
291
        $user->email = $newEmail;
292
293
        if (!$user->update()) {
294
            throw new ModelException((string)current($user->getMessages()));
0 ignored issues
show
Deprecated Code introduced by
The class Canvas\Exception\ModelException has been deprecated with message: version 0.1.5

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
295
        }
296
297
        if (!$userData = $this->loginUsers($user->email, $password)) {
298
            $this->db->rollback();
299
        }
300
301
        $this->db->commit();
302
303
        return $this->response($userData);
304
    }
305
306
    /**
307
     * Login user using Access Token.
308
     * @return Response
309
     */
310
    public function loginBySocial(): Response
311
    {
312
        $request = $this->request->getPostData();
313
314
        $source = Sources::findFirstOrFail([
315
            'title = ?0 and is_deleted = 0',
316
            'bind' => [$request['provider']]
317
        ]);
318
319
        return $this->response($this->providerLogin($source, $request['social_id'], $request));
320
    }
321
322
    /**
323
     * Send the user how filled out the form to the specify email
324
     * a link to reset his password.
325
     *
326
     * @return Response
327
     */
328
    public function recover(): Response
329
    {
330
        $request = $this->request->getPostData();
331
332
        $validation = new CanvasValidation();
333
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
334
335
        $validation->validate($request);
336
337
        $email = $validation->getValue('email');
338
339
        $recoverUser = Users::getByEmail($email);
340
        $recoverUser->generateForgotHash();
341
342
        $recoverUser->notify(new ResetPassword($recoverUser));
343
344
        return $this->response(_('Check your email to recover your password'));
345
    }
346
347
    /**
348
     * Reset the user password.
349
     * @method PUT
350
     * @url /v1/reset
351
     *
352
     * @return Response
353
     */
354
    public function reset(string $key) : Response
355
    {
356
        //is the key empty or does it existe?
357
        if (empty($key) || !$userData = Users::findFirst(['user_activation_forgot = :key:', 'bind' => ['key' => $key]])) {
358
            throw new Exception(_('This Key to reset password doesn\'t exist'));
359
        }
360
361
        $request = $this->request->getPostData();
362
363
        // Get the new password and the verify
364
        $newPassword = trim($request['new_password']);
365
        $verifyPassword = trim($request['verify_password']);
366
367
        //Ok let validate user password
368
        PasswordValidation::validate($newPassword, $verifyPassword);
369
370
        // Has the password and set it
371
        $userData->resetPassword($newPassword);
372
        $userData->user_activation_forgot = '';
373
        $userData->updateOrFail();
374
375
        //log the user out of the site from all devices
376
        $session = new Sessions();
377
        $session->end($userData);
378
379
        $userData->notify(new PasswordUpdate($userData));
380
381
        return $this->response(_('Password Updated'));
382
    }
383
384
    /**
385
    * Set the email config array we are going to be sending.
386
    *
387
    * @todo deprecated move to notifications
388
    * @param String $emailAction
0 ignored issues
show
Bug introduced by
There is no parameter named $emailAction. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
389
    * @param Users  $user
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $user a bit more specific; maybe use BakaUsers.
Loading history...
390
    * @return void
391
    */
392
    protected function sendEmail(BakaUsers $user, string $type): void
393
    {
394
        $send = true;
395
        $subject = null;
396
        $body = null;
397
        switch ($type) {
398
            case 'recover':
399
                $recoveryLink = $this->config->app->frontEndUrl . '/users/reset-password/' . $user->user_activation_forgot;
400
                $subject = _('Password Recovery');
401
                $body = sprintf(_('Click %shere%s to set a new password for your account.'), '<a href="' . $recoveryLink . '" target="_blank">', '</a>');
402
                // send email to recover password
403
                break;
404
            case 'reset':
405
                $activationUrl = $this->config->app->frontEndUrl . '/user/activate/' . $user->user_activation_key;
406
                $subject = _('Password Updated!');
407
                $body = sprintf(_('Your password was update please, use this link to activate your account: %sActivate account%s'), '<a href="' . $activationUrl . '">', '</a>');
408
                // send email that password was update
409
                break;
410
            case 'email-change':
411
                $emailChangeUrl = $this->config->app->frontEndUrl . '/user/' . $user->user_activation_email . '/email';
412
                $subject = _('Email Change Request');
413
                $body = sprintf(_('Click %shere%s to set a new email for your account.'), '<a href="' . $emailChangeUrl . '">', '</a>');
414
                break;
415
            default:
416
                $send = false;
417
                break;
418
        }
419
420
        if ($send) {
421
            $this->mail
422
            ->to($user->email)
423
            ->subject($subject)
424
            ->content($body)
425
            ->sendNow();
426
        }
427
    }
428
}
429