Completed
Pull Request — master (#41)
by Beñat
03:45
created

User::rememberPasswordTokenLifetime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the BenGorUser package.
5
 *
6
 * (c) Beñat Espiña <[email protected]>
7
 * (c) Gorka Laucirica <[email protected]>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace BenGorUser\User\Domain\Model;
14
15
use BenGorUser\User\Domain\Model\Event\UserEnabled;
16
use BenGorUser\User\Domain\Model\Event\UserInvited;
17
use BenGorUser\User\Domain\Model\Event\UserLoggedIn;
18
use BenGorUser\User\Domain\Model\Event\UserLoggedOut;
19
use BenGorUser\User\Domain\Model\Event\UserRegistered;
20
use BenGorUser\User\Domain\Model\Event\UserRememberPasswordRequested;
21
use BenGorUser\User\Domain\Model\Event\UserRoleGranted;
22
use BenGorUser\User\Domain\Model\Event\UserRoleRevoked;
23
use BenGorUser\User\Domain\Model\Exception\UserInactiveException;
24
use BenGorUser\User\Domain\Model\Exception\UserInvitationAlreadyAcceptedException;
25
use BenGorUser\User\Domain\Model\Exception\UserPasswordInvalidException;
26
use BenGorUser\User\Domain\Model\Exception\UserRoleAlreadyGrantedException;
27
use BenGorUser\User\Domain\Model\Exception\UserRoleAlreadyRevokedException;
28
use BenGorUser\User\Domain\Model\Exception\UserRoleInvalidException;
29
use BenGorUser\User\Domain\Model\Exception\UserTokenExpiredException;
30
31
/**
32
 * User domain class.
33
 *
34
 * @author Beñat Espiña <[email protected]>
35
 * @author Gorka Laucirica <[email protected]>
36
 */
37
class User extends UserAggregateRoot
38
{
39
    /**
40
     * The id.
41
     *
42
     * @var UserId
43
     */
44
    protected $id;
45
46
    /**
47
     * The confirmation token.
48
     *
49
     * @var UserToken
50
     */
51
    protected $confirmationToken;
52
53
    /**
54
     * Created on.
55
     *
56
     * @var \DateTimeInterface
57
     */
58
    protected $createdOn;
59
60
    /**
61
     * The email.
62
     *
63
     * @var UserEmail
64
     */
65
    protected $email;
66
67
    /**
68
     * The invitation token.
69
     *
70
     * @var UserToken
71
     */
72
    protected $invitationToken;
73
74
    /**
75
     * The last login.
76
     *
77
     * @var \DateTimeInterface|null
78
     */
79
    protected $lastLogin;
80
81
    /**
82
     * The password.
83
     *
84
     * @var UserPassword
85
     */
86
    protected $password;
87
88
    /**
89
     * The remember password token.
90
     *
91
     * @var UserToken
92
     */
93
    protected $rememberPasswordToken;
94
95
    /**
96
     * Array which contains roles.
97
     *
98
     * @var UserRole[]
99
     */
100
    protected $roles;
101
102
    /**
103
     * Updated on.
104
     *
105
     * @var \DateTimeInterface
106
     */
107
    protected $updatedOn;
108
109
    /**
110
     * Constructor.
111
     *
112
     * @param UserId            $anId      The id
113
     * @param UserEmail         $anEmail   The email
114
     * @param array             $userRoles Array which contains the roles
115
     * @param UserPassword|null $aPassword The encoded password
116
     */
117
    protected function __construct(UserId $anId, UserEmail $anEmail, array $userRoles, UserPassword $aPassword = null)
118
    {
119
        $this->id = $anId;
120
        $this->email = $anEmail;
121
        $this->password = $aPassword;
122
        $this->createdOn = new \DateTimeImmutable();
123
        $this->updatedOn = new \DateTimeImmutable();
124
125
        $this->roles = [];
126
        foreach ($userRoles as $userRole) {
127
            $this->grant($userRole);
128
        }
129
    }
130
131
    /**
132
     * Sign up user.
133
     *
134
     * @param UserId       $anId      The id
135
     * @param UserEmail    $anEmail   The email
136
     * @param UserPassword $aPassword The encoded password
137
     * @param array        $userRoles Array which contains the roles
138
     *
139
     * @return static
140
     */
141 View Code Duplication
    public static function signUp(UserId $anId, UserEmail $anEmail, UserPassword $aPassword, array $userRoles)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
142
    {
143
        $user = new static($anId, $anEmail, $userRoles, $aPassword);
144
        $user->confirmationToken = new UserToken();
145
        $user->publish(
146
            new UserRegistered(
147
                $user->id(),
148
                $user->email(),
149
                $user->confirmationToken()
150
            )
151
        );
152
153
        return $user;
154
    }
155
156
    /**
157
     * Invites user.
158
     *
159
     * @param UserId    $anId      The id
160
     * @param UserEmail $anEmail   The email
161
     * @param array     $userRoles Array which contains the roles
162
     *
163
     * @return static
164
     */
165 View Code Duplication
    public static function invite(UserId $anId, UserEmail $anEmail, array $userRoles)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167
        $user = new static($anId, $anEmail, $userRoles);
168
        $user->invitationToken = new UserToken();
169
        $user->publish(
170
            new UserInvited(
171
                $user->id(),
172
                $user->email(),
173
                $user->invitationToken()
174
            )
175
        );
176
177
        return $user;
178
    }
179
180
    /**
181
     * Gets the id.
182
     *
183
     * @return UserId
184
     */
185
    public function id()
186
    {
187
        return $this->id;
188
    }
189
190
    /**
191
     * Accepts the invitation request.
192
     *
193
     * @throws UserTokenExpiredException when the token is expired
194
     */
195
    public function acceptInvitation()
196
    {
197
        if (null === $this->isInvitationTokenExpired()) {
198
            throw new UserInvitationAlreadyAcceptedException();
199
        }
200
        if (true === $this->isInvitationTokenExpired()) {
201
            throw new UserTokenExpiredException();
202
        }
203
        $this->invitationToken = null;
204
        $this->updatedOn = new \DateTimeImmutable();
205
        $this->publish(
206
            new UserRegistered(
207
                $this->id(),
208
                $this->email()
209
            )
210
        );
211
    }
212
213
    /**
214
     * Updates the user password.
215
     *
216
     * @param UserPassword $aPassword The old password
217
     *
218
     * @throws UserTokenExpiredException when the token is expired
219
     */
220
    public function changePassword(UserPassword $aPassword)
221
    {
222
        if ($this->isRememberPasswordTokenExpired()) {
223
            throw new UserTokenExpiredException();
224
        }
225
226
        $this->password = $aPassword;
227
        $this->rememberPasswordToken = null;
228
        $this->updatedOn = new \DateTimeImmutable();
229
    }
230
231
    /**
232
     * Gets the confirmation token.
233
     *
234
     * @return UserToken
235
     */
236
    public function confirmationToken()
237
    {
238
        return $this->confirmationToken;
239
    }
240
241
    /**
242
     * Gets the created on.
243
     *
244
     * @return \DateTimeInterface
245
     */
246
    public function createdOn()
247
    {
248
        return $this->createdOn;
249
    }
250
251
    /**
252
     * Gets the email.
253
     *
254
     * @return UserEmail
255
     */
256
    public function email()
257
    {
258
        return $this->email;
259
    }
260
261
    /**
262
     * Enables the user account.
263
     */
264
    public function enableAccount()
265
    {
266
        $this->confirmationToken = null;
267
        $this->updatedOn = new \DateTimeImmutable();
268
269
        $this->publish(
270
            new UserEnabled(
271
                $this->id,
272
                $this->email
273
            )
274
        );
275
    }
276
277
    /**
278
     * Adds the given role.
279
     *
280
     * @param UserRole $aRole The user role
281
     *
282
     * @throws UserRoleInvalidException        when the user is role is invalid
283
     * @throws UserRoleAlreadyGrantedException when the user role is already granted
284
     */
285
    public function grant(UserRole $aRole)
286
    {
287
        if (false === $this->isRoleAllowed($aRole)) {
288
            throw new UserRoleInvalidException();
289
        }
290
        if (true === $this->isGranted($aRole)) {
291
            throw new UserRoleAlreadyGrantedException();
292
        }
293
        $this->roles[] = $aRole;
294
        $this->updatedOn = new \DateTimeImmutable();
295
296
        $this->publish(
297
            new UserRoleGranted(
298
                $this->id,
299
                $this->email,
300
                $aRole
301
            )
302
        );
303
    }
304
305
    /**
306
     * Gets the invitation token.
307
     *
308
     * @return UserToken
309
     */
310
    public function invitationToken()
311
    {
312
        return $this->invitationToken;
313
    }
314
315
    /**
316
     * Checks if the user is enabled or not.
317
     *
318
     * @return bool
319
     */
320
    public function isEnabled()
321
    {
322
        return null === $this->confirmationToken || null === $this->confirmationToken->token();
323
    }
324
325
    /**
326
     * Checks if the user has the given role.
327
     *
328
     * @param UserRole $aRole The user role
329
     *
330
     * @return bool
331
     */
332
    public function isGranted(UserRole $aRole)
333
    {
334
        foreach ($this->roles as $role) {
335
            if ($role->equals($aRole)) {
336
                return true;
337
            }
338
        }
339
340
        return false;
341
    }
342
343
    /**
344
     * Checks if the invitation token is expired or not.
345
     *
346
     * @return bool|null
347
     */
348
    public function isInvitationTokenExpired()
349
    {
350
        if ($this->invitationToken instanceof UserToken) {
351
            return $this->invitationToken->isExpired(
352
                $this->invitationTokenLifetime()
353
            );
354
        }
355
    }
356
357
    /**
358
     * Checks if the invitation token is expired or not.
359
     *
360
     * @return bool|null
361
     */
362
    public function isRememberPasswordTokenExpired()
363
    {
364
        if ($this->rememberPasswordToken instanceof UserToken) {
365
            return $this->rememberPasswordToken->isExpired(
366
                $this->rememberPasswordTokenLifetime()
367
            );
368
        }
369
    }
370
371
    /**
372
     * Checks if the role given appears between allowed roles.
373
     *
374
     * @param UserRole $aRole The user role
375
     *
376
     * @return bool
377
     */
378
    public function isRoleAllowed(UserRole $aRole)
379
    {
380
        return in_array($aRole->role(), static::availableRoles(), true);
381
    }
382
383
    /**
384
     * Gets the last login.
385
     *
386
     * @return \DateTimeInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be \DateTimeInterface|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
387
     */
388
    public function lastLogin()
389
    {
390
        return $this->lastLogin;
391
    }
392
393
    /**
394
     * Validates user login for the given password.
395
     *
396
     * @param string              $aPlainPassword Plain password used to log in
397
     * @param UserPasswordEncoder $anEncoder      The encoder used to encode the password
398
     *
399
     * @throws UserInactiveException        when the user is not enabled
400
     * @throws UserPasswordInvalidException when the user password is invalid
401
     */
402
    public function login($aPlainPassword, UserPasswordEncoder $anEncoder)
403
    {
404
        if (false === $this->isEnabled()) {
405
            throw new UserInactiveException();
406
        }
407
        if (false === $this->password()->equals($aPlainPassword, $anEncoder)) {
408
            throw new UserPasswordInvalidException();
409
        }
410
        $this->lastLogin = new \DateTimeImmutable();
411
412
        $this->publish(
413
            new UserLoggedIn(
414
                $this->id,
415
                $this->email
416
            )
417
        );
418
    }
419
420
    /**
421
     * Updated the user state after logout.
422
     *
423
     * @throws UserInactiveException when the user is not enabled
424
     */
425
    public function logout()
426
    {
427
        if (false === $this->isEnabled()) {
428
            throw new UserInactiveException();
429
        }
430
431
        $this->publish(
432
            new UserLoggedOut(
433
                $this->id,
434
                $this->email
435
            )
436
        );
437
    }
438
439
    /**
440
     * Gets the password.
441
     *
442
     * @return UserPassword
443
     */
444
    public function password()
445
    {
446
        return $this->password;
447
    }
448
449
    /**
450
     * Updates the invitation token in case a user has
451
     * been already invited and has lost the token.
452
     *
453
     * @throws UserInvitationAlreadyAcceptedException in case user has already accepted the invitation
454
     */
455
    public function regenerateInvitationToken()
456
    {
457
        if (null === $this->invitationToken) {
458
            throw new UserInvitationAlreadyAcceptedException();
459
        }
460
        $this->invitationToken = new UserToken();
461
    }
462
463
    /**
464
     * Gets the remember password token.
465
     *
466
     * @return UserToken
467
     */
468
    public function rememberPasswordToken()
469
    {
470
        return $this->rememberPasswordToken;
471
    }
472
473
    /**
474
     * Remembers the password.
475
     */
476
    public function rememberPassword()
477
    {
478
        $this->rememberPasswordToken = new UserToken();
479
480
        $this->publish(
481
            new UserRememberPasswordRequested(
482
                $this->id,
483
                $this->email,
484
                $this->rememberPasswordToken
485
            )
486
        );
487
    }
488
489
    /**
490
     * Removes the given role.
491
     *
492
     * @param UserRole $aRole The user role
493
     *
494
     * @throws UserRoleInvalidException        when the role is invalid
495
     * @throws UserRoleAlreadyRevokedException when the role is already revoked
496
     */
497
    public function revoke(UserRole $aRole)
498
    {
499
        if (false === $this->isRoleAllowed($aRole)) {
500
            throw new UserRoleInvalidException();
501
        }
502
        foreach ($this->roles as $key => $role) {
503
            if ($role->equals($aRole)) {
504
                unset($this->roles[$key]);
505
                $this->roles = array_values($this->roles);
506
                break;
507
            }
508
            throw new UserRoleAlreadyRevokedException();
509
        }
510
        $this->updatedOn = new \DateTimeImmutable();
511
        $this->publish(
512
            new UserRoleRevoked(
513
                $this->id,
514
                $this->email,
515
                $aRole
516
            )
517
        );
518
    }
519
520
    /**
521
     * Gets the roles.
522
     *
523
     * @return UserRole[]
524
     */
525
    public function roles()
526
    {
527
        return $this->roles;
528
    }
529
530
    /**
531
     * Gets the updated on.
532
     *
533
     * @return \DateTimeInterface
534
     */
535
    public function updatedOn()
536
    {
537
        return $this->updatedOn;
538
    }
539
540
    /**
541
     * Gets the available roles in scalar type.
542
     *
543
     * This method is an extension point that it allows
544
     * to add more roles easily in the domain.
545
     *
546
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
547
     */
548
    public static function availableRoles()
549
    {
550
        return ['ROLE_USER', 'ROLE_ADMIN'];
551
    }
552
553
    /**
554
     * Extension point that determines the lifetime
555
     * of the invitation token in seconds.
556
     *
557
     * @return int
558
     */
559
    protected function invitationTokenLifetime()
560
    {
561
        return 604800; // 1 week
562
    }
563
564
    /**
565
     * Extension point that determines the lifetime
566
     * of the remember password token in second.
567
     *
568
     * @return int
569
     */
570
    protected function rememberPasswordTokenLifetime()
571
    {
572
        return 3600; // 1 hour
573
    }
574
}
575