Completed
Pull Request — master (#41)
by Beñat
04:00
created

User::invitationTokenLifetime()   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
use BenGorUser\User\Domain\Model\Exception\UserTokenNotFoundException;
31
32
/**
33
 * User domain class.
34
 *
35
 * @author Beñat Espiña <[email protected]>
36
 * @author Gorka Laucirica <[email protected]>
37
 */
38
class User extends UserAggregateRoot
39
{
40
    /**
41
     * The id.
42
     *
43
     * @var UserId
44
     */
45
    protected $id;
46
47
    /**
48
     * The confirmation token.
49
     *
50
     * @var UserToken
51
     */
52
    protected $confirmationToken;
53
54
    /**
55
     * Created on.
56
     *
57
     * @var \DateTimeInterface
58
     */
59
    protected $createdOn;
60
61
    /**
62
     * The email.
63
     *
64
     * @var UserEmail
65
     */
66
    protected $email;
67
68
    /**
69
     * The invitation token.
70
     *
71
     * @var UserToken
72
     */
73
    protected $invitationToken;
74
75
    /**
76
     * The last login.
77
     *
78
     * @var \DateTimeInterface|null
79
     */
80
    protected $lastLogin;
81
82
    /**
83
     * The password.
84
     *
85
     * @var UserPassword
86
     */
87
    protected $password;
88
89
    /**
90
     * The remember password token.
91
     *
92
     * @var UserToken
93
     */
94
    protected $rememberPasswordToken;
95
96
    /**
97
     * Array which contains roles.
98
     *
99
     * @var UserRole[]
100
     */
101
    protected $roles;
102
103
    /**
104
     * Updated on.
105
     *
106
     * @var \DateTimeInterface
107
     */
108
    protected $updatedOn;
109
110
    /**
111
     * Constructor.
112
     *
113
     * @param UserId            $anId      The id
114
     * @param UserEmail         $anEmail   The email
115
     * @param array             $userRoles Array which contains the roles
116
     * @param UserPassword|null $aPassword The encoded password
117
     */
118
    protected function __construct(UserId $anId, UserEmail $anEmail, array $userRoles, UserPassword $aPassword = null)
119
    {
120
        $this->id = $anId;
121
        $this->email = $anEmail;
122
        $this->password = $aPassword;
123
        $this->createdOn = new \DateTimeImmutable();
124
        $this->updatedOn = new \DateTimeImmutable();
125
126
        $this->roles = [];
127
        foreach ($userRoles as $userRole) {
128
            $this->grant($userRole);
129
        }
130
    }
131
132
    /**
133
     * Sign up user.
134
     *
135
     * @param UserId       $anId      The id
136
     * @param UserEmail    $anEmail   The email
137
     * @param UserPassword $aPassword The encoded password
138
     * @param array        $userRoles Array which contains the roles
139
     *
140
     * @return static
141
     */
142 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...
143
    {
144
        $user = new static($anId, $anEmail, $userRoles, $aPassword);
145
        $user->confirmationToken = new UserToken();
146
        $user->publish(
147
            new UserRegistered(
148
                $user->id(),
149
                $user->email(),
150
                $user->confirmationToken()
151
            )
152
        );
153
154
        return $user;
155
    }
156
157
    /**
158
     * Invites user.
159
     *
160
     * @param UserId    $anId      The id
161
     * @param UserEmail $anEmail   The email
162
     * @param array     $userRoles Array which contains the roles
163
     *
164
     * @return static
165
     */
166 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...
167
    {
168
        $user = new static($anId, $anEmail, $userRoles);
169
        $user->invitationToken = new UserToken();
170
        $user->publish(
171
            new UserInvited(
172
                $user->id(),
173
                $user->email(),
174
                $user->invitationToken()
175
            )
176
        );
177
178
        return $user;
179
    }
180
181
    /**
182
     * Gets the id.
183
     *
184
     * @return UserId
185
     */
186
    public function id()
187
    {
188
        return $this->id;
189
    }
190
191
    /**
192
     * Accepts the invitation request.
193
     *
194
     * @throws UserTokenExpiredException when the token is expired
195
     */
196
    public function acceptInvitation()
197
    {
198
        if ($this->isInvitationTokenAccepted()) {
199
            throw new UserInvitationAlreadyAcceptedException();
200
        }
201
        if ($this->isInvitationTokenExpired()) {
202
            throw new UserTokenExpiredException();
203
        }
204
        $this->invitationToken = null;
205
        $this->updatedOn = new \DateTimeImmutable();
206
        $this->publish(
207
            new UserRegistered(
208
                $this->id(),
209
                $this->email()
210
            )
211
        );
212
    }
213
214
    /**
215
     * Updates the user password.
216
     *
217
     * @param UserPassword $aPassword The old password
218
     *
219
     * @throws UserTokenExpiredException when the token is expired
220
     */
221
    public function changePassword(UserPassword $aPassword)
222
    {
223
        $this->password = $aPassword;
224
        $this->rememberPasswordToken = null;
225
        $this->updatedOn = new \DateTimeImmutable();
226
    }
227
228
    /**
229
     * Gets the confirmation token.
230
     *
231
     * @return UserToken
232
     */
233
    public function confirmationToken()
234
    {
235
        return $this->confirmationToken;
236
    }
237
238
    /**
239
     * Gets the created on.
240
     *
241
     * @return \DateTimeInterface
242
     */
243
    public function createdOn()
244
    {
245
        return $this->createdOn;
246
    }
247
248
    /**
249
     * Gets the email.
250
     *
251
     * @return UserEmail
252
     */
253
    public function email()
254
    {
255
        return $this->email;
256
    }
257
258
    /**
259
     * Enables the user account.
260
     */
261
    public function enableAccount()
262
    {
263
        $this->confirmationToken = null;
264
        $this->updatedOn = new \DateTimeImmutable();
265
266
        $this->publish(
267
            new UserEnabled(
268
                $this->id,
269
                $this->email
270
            )
271
        );
272
    }
273
274
    /**
275
     * Adds the given role.
276
     *
277
     * @param UserRole $aRole The user role
278
     *
279
     * @throws UserRoleInvalidException        when the user is role is invalid
280
     * @throws UserRoleAlreadyGrantedException when the user role is already granted
281
     */
282
    public function grant(UserRole $aRole)
283
    {
284
        if (false === $this->isRoleAllowed($aRole)) {
285
            throw new UserRoleInvalidException();
286
        }
287
        if (true === $this->isGranted($aRole)) {
288
            throw new UserRoleAlreadyGrantedException();
289
        }
290
        $this->roles[] = $aRole;
291
        $this->updatedOn = new \DateTimeImmutable();
292
293
        $this->publish(
294
            new UserRoleGranted(
295
                $this->id,
296
                $this->email,
297
                $aRole
298
            )
299
        );
300
    }
301
302
    /**
303
     * Gets the invitation token.
304
     *
305
     * @return UserToken
306
     */
307
    public function invitationToken()
308
    {
309
        return $this->invitationToken;
310
    }
311
312
    /**
313
     * Checks if the user is enabled or not.
314
     *
315
     * @return bool
316
     */
317
    public function isEnabled()
318
    {
319
        return null === $this->confirmationToken || null === $this->confirmationToken->token();
320
    }
321
322
    /**
323
     * Checks if the user has the given role.
324
     *
325
     * @param UserRole $aRole The user role
326
     *
327
     * @return bool
328
     */
329
    public function isGranted(UserRole $aRole)
330
    {
331
        foreach ($this->roles as $role) {
332
            if ($role->equals($aRole)) {
333
                return true;
334
            }
335
        }
336
337
        return false;
338
    }
339
340
    /**
341
     * Checks if the invitation token is accepted or not.
342
     *
343
     * @return bool
344
     */
345
    public function isInvitationTokenAccepted()
346
    {
347
        return null === $this->invitationToken;
348
    }
349
350
    /**
351
     * Checks if the invitation token is expired or not.
352
     *
353
     * @throws UserTokenNotFoundException when the invitation token does not exist
354
     *
355
     * @return bool
356
     */
357
    public function isInvitationTokenExpired()
358
    {
359
        if (!$this->invitationToken instanceof UserToken) {
360
            throw new UserTokenNotFoundException();
361
        }
362
363
        return $this->invitationToken->isExpired(
364
            $this->invitationTokenLifetime()
365
        );
366
    }
367
368
    /**
369
     * Checks if the remember password token is expired or not.
370
     *
371
     * @throws UserTokenNotFoundException when the remember password token does not exist
372
     *
373
     * @return bool
374
     */
375
    public function isRememberPasswordTokenExpired()
376
    {
377
        if (!$this->rememberPasswordToken instanceof UserToken) {
378
            throw new UserTokenNotFoundException();
379
        }
380
381
        return $this->rememberPasswordToken->isExpired(
382
            $this->rememberPasswordTokenLifetime()
383
        );
384
    }
385
386
    /**
387
     * Checks if the role given appears between allowed roles.
388
     *
389
     * @param UserRole $aRole The user role
390
     *
391
     * @return bool
392
     */
393
    public function isRoleAllowed(UserRole $aRole)
394
    {
395
        return in_array($aRole->role(), static::availableRoles(), true);
396
    }
397
398
    /**
399
     * Gets the last login.
400
     *
401
     * @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...
402
     */
403
    public function lastLogin()
404
    {
405
        return $this->lastLogin;
406
    }
407
408
    /**
409
     * Validates user login for the given password.
410
     *
411
     * @param string              $aPlainPassword Plain password used to log in
412
     * @param UserPasswordEncoder $anEncoder      The encoder used to encode the password
413
     *
414
     * @throws UserInactiveException        when the user is not enabled
415
     * @throws UserPasswordInvalidException when the user password is invalid
416
     */
417
    public function login($aPlainPassword, UserPasswordEncoder $anEncoder)
418
    {
419
        if (false === $this->isEnabled()) {
420
            throw new UserInactiveException();
421
        }
422
        if (false === $this->password()->equals($aPlainPassword, $anEncoder)) {
423
            throw new UserPasswordInvalidException();
424
        }
425
        $this->lastLogin = new \DateTimeImmutable();
426
427
        $this->publish(
428
            new UserLoggedIn(
429
                $this->id,
430
                $this->email
431
            )
432
        );
433
    }
434
435
    /**
436
     * Updated the user state after logout.
437
     *
438
     * @throws UserInactiveException when the user is not enabled
439
     */
440
    public function logout()
441
    {
442
        if (false === $this->isEnabled()) {
443
            throw new UserInactiveException();
444
        }
445
446
        $this->publish(
447
            new UserLoggedOut(
448
                $this->id,
449
                $this->email
450
            )
451
        );
452
    }
453
454
    /**
455
     * Gets the password.
456
     *
457
     * @return UserPassword
458
     */
459
    public function password()
460
    {
461
        return $this->password;
462
    }
463
464
    /**
465
     * Updates the invitation token in case a user has
466
     * been already invited and has lost the token.
467
     *
468
     * @throws UserInvitationAlreadyAcceptedException in case user has already accepted the invitation
469
     */
470
    public function regenerateInvitationToken()
471
    {
472
        if (null === $this->invitationToken) {
473
            throw new UserInvitationAlreadyAcceptedException();
474
        }
475
        $this->invitationToken = new UserToken();
476
    }
477
478
    /**
479
     * Gets the remember password token.
480
     *
481
     * @return UserToken
482
     */
483
    public function rememberPasswordToken()
484
    {
485
        return $this->rememberPasswordToken;
486
    }
487
488
    /**
489
     * Remembers the password.
490
     */
491
    public function rememberPassword()
492
    {
493
        $this->rememberPasswordToken = new UserToken();
494
495
        $this->publish(
496
            new UserRememberPasswordRequested(
497
                $this->id,
498
                $this->email,
499
                $this->rememberPasswordToken
500
            )
501
        );
502
    }
503
504
    /**
505
     * Removes the given role.
506
     *
507
     * @param UserRole $aRole The user role
508
     *
509
     * @throws UserRoleInvalidException        when the role is invalid
510
     * @throws UserRoleAlreadyRevokedException when the role is already revoked
511
     */
512
    public function revoke(UserRole $aRole)
513
    {
514
        if (false === $this->isRoleAllowed($aRole)) {
515
            throw new UserRoleInvalidException();
516
        }
517
        foreach ($this->roles as $key => $role) {
518
            if ($role->equals($aRole)) {
519
                unset($this->roles[$key]);
520
                $this->roles = array_values($this->roles);
521
                break;
522
            }
523
            throw new UserRoleAlreadyRevokedException();
524
        }
525
        $this->updatedOn = new \DateTimeImmutable();
526
        $this->publish(
527
            new UserRoleRevoked(
528
                $this->id,
529
                $this->email,
530
                $aRole
531
            )
532
        );
533
    }
534
535
    /**
536
     * Gets the roles.
537
     *
538
     * @return UserRole[]
539
     */
540
    public function roles()
541
    {
542
        return $this->roles;
543
    }
544
545
    /**
546
     * Gets the updated on.
547
     *
548
     * @return \DateTimeInterface
549
     */
550
    public function updatedOn()
551
    {
552
        return $this->updatedOn;
553
    }
554
555
    /**
556
     * Gets the available roles in scalar type.
557
     *
558
     * This method is an extension point that it allows
559
     * to add more roles easily in the domain.
560
     *
561
     * @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...
562
     */
563
    public static function availableRoles()
564
    {
565
        return ['ROLE_USER', 'ROLE_ADMIN'];
566
    }
567
568
    /**
569
     * Extension point that determines the lifetime
570
     * of the invitation token in seconds.
571
     *
572
     * @return int
573
     */
574
    protected function invitationTokenLifetime()
575
    {
576
        return 604800; // 1 week
577
    }
578
579
    /**
580
     * Extension point that determines the lifetime
581
     * of the remember password token in seconds.
582
     *
583
     * @return int
584
     */
585
    protected function rememberPasswordTokenLifetime()
586
    {
587
        return 3600; // 1 hour
588
    }
589
}
590