Failed Conditions
Pull Request — master (#229)
by Rafael
03:09
created

AuthController::changeUserEmail()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 1
dl 0
loc 48
ccs 0
cts 24
cp 0
crap 20
rs 9.1344
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
use Lcobucci\JWT\Builder;
29
use Lcobucci\JWT\Signer\Hmac\Sha512;
30
31
/**
32
 * Class AuthController.
33
 *
34
 * @package Canvas\Api\Controllers
35
 *
36
 * @property Users $userData
37
 * @property Request $request
38
 * @property Config $config
39
 * @property \Baka\Mail\Message $mail
40
 * @property Apps $app
41
 */
42
class AuthController extends \Baka\Auth\AuthController
43
{
44
    /**
45
     * Auth Trait.
46
     */
47
    use AuthTrait;
48
    use TokenTrait;
49
    use SocialLoginTrait;
50
51
    /**
52
     * Setup for this controller.
53
     *
54
     * @return void
55
     */
56
    public function onConstruct()
57
    {
58
        $this->userLinkedSourcesModel = new UserLinkedSources();
59
        $this->userModel = new Users();
60
61
        if (!isset($this->config->jwt)) {
62
            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...
63
        }
64
    }
65
66
    /**
67
     * User Login.
68
     * @method POST
69
     * @url /v1/auth
70
     *
71
     * @return Response
72
     */
73
    public function login() : Response
74
    {
75
        $request = $this->request->getPostData();
76
77
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
78
        $admin = 0;
79
        $remember = 1;
80
81
        //Ok let validate user password
82
        $validation = new CanvasValidation();
83
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid')]));
84
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
85
86
        $validation->setFilters('name', 'trim');
87
        $validation->setFilters('password', 'trim');
88
89
        //validate this form for password
90
        $validation->validate($request);
91
92
        $email = $validation->getValue('email');
93
        $password = $validation->getValue('password');
94
95
        /**
96
         * Login the user via ecosystem or app.
97
         */
98
        $auth = Factory::create($this->app->ecosystemAuth());
99
        $userData = $auth::login($email, $password, $remember, $admin, $userIp);
100
        $token = $userData->getToken();
101
102
        //start session
103
        $session = new Sessions();
104
        $session->start($userData, $token['sessionId'], $token['token'], $userIp, 1);
105
106
        return $this->response([
107
            'token' => $token['token'],
108
            'refresh_token' => $token['refresh_token'],
109
            'time' => date('Y-m-d H:i:s'),
110
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
111
            'refresh_token_expires' => date('Y-m-d H:i:s', time() + 31536000),
112
            'id' => $userData->getId()
113
        ]);
114
    }
115
116
    /**
117
     * User Signup.
118
     *
119
     * @method POST
120
     * @url /v1/users
121
     *
122
     * @return Response
123
     */
124
    public function signup() : Response
125
    {
126
        $user = $this->userModel;
127
128
        $request = $this->request->getPostData();
129
130
        //Ok let validate user password
131
        $validation = new CanvasValidation();
132
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
133
        $validation->add('firstname', new PresenceOf(['message' => _('The firstname is required.')]));
134
        $validation->add('lastname', new PresenceOf(['message' => _('The lastname is required.')]));
135
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
136
137
        $validation->add(
138
            'password',
139
            new StringLength([
140
                'min' => 8,
141
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
142
            ])
143
        );
144
145
        $validation->add('password', new Confirmation([
146
            'message' => _('Password and confirmation do not match.'),
147
            'with' => 'verify_password',
148
        ]));
149
150
        $validation->setFilters('password', 'trim');
151
        $validation->setFilters('displayname', 'trim');
152
        $validation->setFilters('default_company', 'trim');
153
154
        //validate this form for password
155
        $validation->validate($request);
156
157
        $user->email = $validation->getValue('email');
158
        $user->firstname = $validation->getValue('firstname');
159
        $user->lastname = $validation->getValue('lastname');
160
        $user->password = $validation->getValue('password');
161
        $userIp = !defined('API_TESTS') ? $this->request->getClientAddress() : '127.0.0.1'; //help getting the client ip on scrutinizer :(
162
        $user->displayname = $validation->getValue('displayname');
163
        $user->defaultCompanyName = $validation->getValue('default_company');
164
165
        //user registration
166
        try {
167
            $this->db->begin();
168
169
            $user->signUp();
170
171
            $this->db->commit();
172
        } catch (Exception $e) {
173
            $this->db->rollback();
174
175
            throw new Exception($e->getMessage());
176
        }
177
178
        $token = $user->getToken();
179
180
        //start session
181
        $session = new Sessions();
182
        $session->start($user, $token['sessionId'], $token['token'], $userIp, 1);
183
184
        $authSession = [
185
            'token' => $token['token'],
186
            'time' => date('Y-m-d H:i:s'),
187
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
188
            'id' => $user->getId(),
189
        ];
190
191
        $user->password = null;
192
        $this->sendEmail($user, 'signup');
193
194
        return $this->response([
195
            'user' => $user,
196
            'session' => $authSession
197
        ]);
198
    }
199
200
    /**
201
     * Refresh user auth.
202
     *
203
     * @return Response
204
     * @todo Validate acces_token and refresh token, session's user email and relogin
205
     */
206
    public function refresh(): Response
207
    {
208
        $request = $this->request->getPostData();
209
        $accessToken = $this->getToken($request['access_token']);
210
        $refreshToken = $this->getToken($request['refresh_token']);
211
212
        if (time() != $accessToken->getClaim('exp')) {
213
            throw new ServerErrorHttpException('Issued Access Token has not expired');
1 ignored issue
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...
214
        }
215
216
        //Check if both tokens relate to the same user's email
217
        if ($accessToken->getClaim('sessionId') == $refreshToken->getClaim('sessionId')) {
218
            $user = Users::getByEmail($accessToken->getClaim('email'));
219
        }
220
221
        $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...
222
223
        return $this->response([
224
            'token' => $token['token'],
225
            'time' => date('Y-m-d H:i:s'),
226
            'expires' => date('Y-m-d H:i:s', time() + $this->config->jwt->payload->exp),
227
            'id' => $user->getId(),
228
        ]);
229
    }
230
231
    /**
232
     * Send email to change current email for user.
233
     * @param int $id
234
     * @return Response
235
     */
236
    public function sendEmailChange(int $id): Response
237
    {
238
        //Search for user
239
        $user = Users::getById($id);
240
241
        if (!is_object($user)) {
242
            throw new NotFoundHttpException(_('User not found'));
243
        }
244
245
        //Send email
246
        $this->sendEmail($user, 'email-change');
247
248
        return $this->response($user);
249
    }
250
251
    /**
252
     * Change user's email.
253
     * @param string $hash
254
     * @return Response
255
     */
256
    public function changeUserEmail(string $hash): Response
257
    {
258
        $request = $this->request->getPostData();
259
260
        //Ok let validate user password
261
        $validation = new CanvasValidation();
262
        $validation->add('password', new PresenceOf(['message' => _('The password is required.')]));
263
        $validation->add('new_email', new EmailValidator(['message' => _('The email is not valid.')]));
264
265
        $validation->add(
266
            'password',
267
            new StringLength([
268
                'min' => 8,
269
                'messageMinimum' => _('Password is too short. Minimum 8 characters.'),
270
            ])
271
        );
272
273
        //validate this form for password
274
        $validation->setFilters('password', 'trim');
275
        $validation->setFilters('default_company', 'trim');
276
        $validation->validate($request);
277
278
        $newEmail = $validation->getValue('new_email');
279
        $password = $validation->getValue('password');
280
281
        //Search user by key
282
        $user = Users::getByUserActivationEmail($hash);
283
284
        if (!is_object($user)) {
285
            throw new NotFoundHttpException(_('User not found'));
286
        }
287
288
        $this->db->begin();
289
290
        $user->email = $newEmail;
291
292
        if (!$user->update()) {
293
            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...
294
        }
295
296
        if (!$userData = $this->loginUsers($user->email, $password)) {
297
            $this->db->rollback();
298
        }
299
300
        $this->db->commit();
301
302
        return $this->response($userData);
303
    }
304
305
    /**
306
     * Login user using Access Token.
307
     * @return Response
308
     */
309
    public function loginBySocial(): Response
310
    {
311
        $request = $this->request->getPostData();
312
313
        $source = Sources::findFirstOrFail([
314
            'title = ?0 and is_deleted = 0',
315
            'bind' => [$request['provider']]
316
        ]);
317
318
        return $this->response($this->providerLogin($source, $request['social_id'], $request));
319
    }
320
321
    /**
322
     * Send the user how filled out the form to the specify email
323
     * a link to reset his password.
324
     *
325
     * @return Response
326
     */
327
    public function recover(): Response
328
    {
329
        $request = $this->request->getPostData();
330
331
        $validation = new CanvasValidation();
332
        $validation->add('email', new EmailValidator(['message' => _('The email is not valid.')]));
333
334
        $validation->validate($request);
335
336
        $email = $validation->getValue('email');
337
338
        $recoverUser = Users::getByEmail($email);
339
        $recoverUser->generateForgotHash();
340
341
        $recoverUser->notify(new ResetPassword($recoverUser));
342
343
        return $this->response(_('Check your email to recover your password'));
344
    }
345
346
    /**
347
     * Reset the user password.
348
     * @method PUT
349
     * @url /v1/reset
350
     *
351
     * @return Response
352
     */
353
    public function reset(string $key) : Response
354
    {
355
        //is the key empty or does it existe?
356
        if (empty($key) || !$userData = Users::findFirst(['user_activation_forgot = :key:', 'bind' => ['key' => $key]])) {
357
            throw new Exception(_('This Key to reset password doesn\'t exist'));
358
        }
359
360
        $request = $this->request->getPostData();
361
362
        // Get the new password and the verify
363
        $newPassword = trim($request['new_password']);
364
        $verifyPassword = trim($request['verify_password']);
365
366
        //Ok let validate user password
367
        PasswordValidation::validate($newPassword, $verifyPassword);
368
369
        // Has the password and set it
370
        $userData->resetPassword($newPassword);
371
        $userData->user_activation_forgot = '';
372
        $userData->updateOrFail();
373
374
        //log the user out of the site from all devices
375
        $session = new Sessions();
376
        $session->end($userData);
377
378
        $userData->notify(new PasswordUpdate($userData));
379
380
        return $this->response(_('Password Updated'));
381
    }
382
383
    /**
384
    * Set the email config array we are going to be sending.
385
    *
386
    * @todo deprecated move to notifications
387
    * @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...
388
    * @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...
389
    * @return void
390
    */
391
    protected function sendEmail(BakaUsers $user, string $type): void
392
    {
393
        $send = true;
394
        $subject = null;
395
        $body = null;
396
        switch ($type) {
397
            case 'recover':
398
                $recoveryLink = $this->config->app->frontEndUrl . '/users/reset-password/' . $user->user_activation_forgot;
399
                $subject = _('Password Recovery');
400
                $body = sprintf(_('Click %shere%s to set a new password for your account.'), '<a href="' . $recoveryLink . '" target="_blank">', '</a>');
401
                // send email to recover password
402
                break;
403
            case 'reset':
404
                $activationUrl = $this->config->app->frontEndUrl . '/user/activate/' . $user->user_activation_key;
405
                $subject = _('Password Updated!');
406
                $body = sprintf(_('Your password was update please, use this link to activate your account: %sActivate account%s'), '<a href="' . $activationUrl . '">', '</a>');
407
                // send email that password was update
408
                break;
409
            case 'email-change':
410
                $emailChangeUrl = $this->config->app->frontEndUrl . '/user/' . $user->user_activation_email . '/email';
411
                $subject = _('Email Change Request');
412
                $body = sprintf(_('Click %shere%s to set a new email for your account.'), '<a href="' . $emailChangeUrl . '">', '</a>');
413
                break;
414
            default:
415
                $send = false;
416
                break;
417
        }
418
419
        if ($send) {
420
            $this->mail
421
            ->to($user->email)
422
            ->subject($subject)
423
            ->content($body)
424
            ->sendNow();
425
        }
426
    }
427
}
428