User::resetPassword()   B
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 12
nc 4
nop 3
1
<?php
2
3
declare (strict_types = 1);
4
5
namespace HMLB\UserBundle\User;
6
7
use DateTime;
8
use HMLB\DDD\Entity\AggregateRoot;
9
use HMLB\DDD\Entity\Identity;
10
use HMLB\DDD\Validation\Assertion;
11
use HMLB\UserBundle\Event\EmailChanged;
12
use HMLB\UserBundle\Event\EmailConfirmed;
13
use HMLB\UserBundle\Event\EmailValidationRequested;
14
use HMLB\UserBundle\Event\PasswordChanged;
15
use HMLB\UserBundle\Event\PasswordReset;
16
use HMLB\UserBundle\Event\PasswordResetRequested;
17
use HMLB\UserBundle\Event\UserRegistered;
18
use HMLB\UserBundle\Exception\EmailAlreadyConfirmedException;
19
use HMLB\UserBundle\Exception\InvalidEmailConfirmationTokenException;
20
use HMLB\UserBundle\Exception\InvalidPasswordResettingTokenException;
21
use HMLB\UserBundle\Exception\PasswordResettingNotRequestedException;
22
use SimpleBus\Message\Recorder\ContainsRecordedMessages;
23
use SimpleBus\Message\Recorder\PrivateMessageRecorderCapabilities;
24
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
25
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
26
27
class User implements AdvancedUserInterface, AggregateRoot, ContainsRecordedMessages
28
{
29
    use PrivateMessageRecorderCapabilities;
30
31
    /**
32
     * @var Identity
33
     */
34
    protected $id;
35
36
    /**
37
     * @var Role[]
38
     */
39
    protected $roles = [];
40
41
    /**
42
     * @var string
43
     */
44
    protected $username;
45
46
    /**
47
     * @var string
48
     */
49
    protected $usernameCanonical;
50
51
    /**
52
     * @var string
53
     */
54
    protected $email;
55
56
    /**
57
     * @var string
58
     */
59
    protected $emailCanonical;
60
61
    /**
62
     * @var bool
63
     */
64
    protected $enabled = false;
65
66
    /**
67
     * The salt to use for hashing.
68
     *
69
     * @var string
70
     */
71
    protected $salt;
72
73
    /**
74
     * Encrypted password. Must be persisted.
75
     *
76
     * @var string
77
     */
78
    protected $password;
79
80
    /**
81
     * @var DateTime
82
     */
83
    protected $lastLogin;
84
85
    /**
86
     * Random string sent to the user email address in order to verify it.
87
     *
88
     * @var string
89
     */
90
    protected $confirmationToken;
91
92
    /**
93
     * Random string sent to the user email address in order to reset the password.
94
     *
95
     * @var string
96
     */
97
    protected $resettingToken;
98
99
    /**
100
     * @var DateTime
101
     */
102
    protected $passwordRequestedAt;
103
104
    /**
105
     * @var DateTime
106
     */
107
    protected $created;
108
109
    /**
110
     * @var DateTime
111
     */
112
    protected $updated;
113
114
    /**
115
     * @var bool
116
     */
117
    protected $locked = false;
118
119
    /**
120
     * @var bool
121
     */
122
    protected $expired = false;
123
124
    /**
125
     * @var DateTime
126
     */
127
    protected $expiresAt;
128
129
    /**
130
     * @var bool
131
     */
132
    protected $credentialsExpired = false;
133
134
    /**
135
     * @var DateTime
136
     */
137
    protected $credentialsExpireAt;
138
139
    /**
140
     * @param string                       $username
141
     * @param string                       $email
142
     * @param string                       $plainPassword
143
     * @param UserPasswordEncoderInterface $encoder
144
     * @param array                        $roles
145
     * @param bool                         $enable
146
     */
147
    protected function __construct(
148
        string $username,
149
        string $email,
150
        string $plainPassword,
151
        UserPasswordEncoderInterface $encoder,
152
        array $roles = [],
153
        bool $enable = true
154
    ) {
155
        Assertion::email($email);
156
        $this->id = new Identity();
157
        $this->roles = $roles;
158
        $this->salt = $this->generateToken();
159
        $this->confirmationToken = $this->generateToken();
160
        $this->created = new DateTime();
161
        $this->username = $username;
162
        $this->email = $email;
163
        if ($enable) {
164
            $this->enable();
165
        }
166
        $this->updateCanonicalFields();
167
        $this->updatePassword($plainPassword, $encoder);
168
    }
169
170
    public static function register(
171
        $username,
172
        $email,
173
        $plainPassword,
174
        UserPasswordEncoderInterface $encoder,
175
        array $roles = ['ROLE_USER']
176
    ): User
177
    {
178
        $user = new static($username, $email, $plainPassword, $encoder, $roles);
179
        $user->recordUserRegistered();
180
        $user->enabled = true;
181
182
        return $user;
183
    }
184
185
    /**
186
     * Getter de id.
187
     *
188
     * @return Identity
189
     */
190
    public function getId(): Identity
191
    {
192
        return is_string($this->id) ? new Identity($this->id) : $this->id;
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function getRoles(): array
199
    {
200
        return $this->roles;
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function getPassword(): string
207
    {
208
        return $this->password;
209
    }
210
211
    /**
212
     * Returns the salt that was originally used to encode the password.
213
     *
214
     * This can return null if the password was not encoded using a salt.
215
     *
216
     * @return string|null The salt
217
     */
218
    public function getSalt(): string
219
    {
220
        return $this->salt;
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226
    public function getUsername(): string
227
    {
228
        return $this->username;
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234
    public function eraseCredentials()
235
    {
236
        //No-op
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function isAccountNonExpired(): bool
243
    {
244
        if (true === $this->expired) {
245
            return false;
246
        }
247
248
        if (null !== $this->expiresAt && $this->expiresAt->getTimestamp() < time()) {
249
            return false;
250
        }
251
252
        return true;
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function isAccountNonLocked(): bool
259
    {
260
        return !$this->locked;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266
    public function isCredentialsNonExpired(): bool
267
    {
268
        if (true === $this->credentialsExpired) {
269
            return false;
270
        }
271
272
        if (null !== $this->credentialsExpireAt && $this->credentialsExpireAt->getTimestamp() < time()) {
273
            return false;
274
        }
275
276
        return true;
277
    }
278
279
    /**
280
     * Change the email address of the user.
281
     *
282
     * @param string $email
283
     */
284
    public function changeEmail(string $email)
285
    {
286
        Assertion::email($email);
287
        $oldEmail = $this->email;
288
        $this->email = $email;
289
        $this->emailCanonical = self::canonicalize($this->email);
290
        $this->updated = new DateTime();
291
        $this->record(new EmailChanged($this->getId(), $oldEmail, $this->email));
292
    }
293
294
    /**
295
     * @param string                       $password
296
     * @param UserPasswordEncoderInterface $encoder
297
     */
298
    public function changePassword(string $password, UserPasswordEncoderInterface $encoder)
299
    {
300
        $oldPassword = $this->password;
301
        $this->updatePassword($password, $encoder);
302
        $this->updated = new DateTime();
303
        $this->record(new PasswordChanged($this->id, $oldPassword, $password));
304
    }
305
306
    /**
307
     * {@inheritdoc}
308
     */
309
    public function isEnabled(): bool
310
    {
311
        return $this->enabled;
312
    }
313
314
    /**
315
     * Enable.
316
     */
317
    public function enable()
318
    {
319
        $this->enabled = true;
320
    }
321
322
    /**
323
     * Disable.
324
     */
325
    public function disable()
326
    {
327
        $this->enabled = false;
328
    }
329
330
    /**
331
     * @return bool
332
     */
333
    public function isEmailConfirmed(): bool
334
    {
335
        return null === $this->confirmationToken;
336
    }
337
338
    public function requestEmailConfirmation()
339
    {
340
        $this->confirmationToken = $this->generateToken();
341
        $this->updated = new DateTime();
342
        $this->record(new EmailValidationRequested($this->id, $this->confirmationToken));
343
    }
344
345
    public function confirmEmail(string $confirmationToken)
346
    {
347
        if ($this->isEmailConfirmed()) {
348
            throw new EmailAlreadyConfirmedException();
349
        }
350
351
        if ($confirmationToken !== $this->confirmationToken) {
352
            throw new InvalidEmailConfirmationTokenException();
353
        }
354
355
        $this->confirmationToken = null;
356
        $this->updated = new DateTime();
357
        $this->record(new EmailConfirmed($this->id, $this->email));
358
    }
359
360
    public function isPasswordResetRequested(): bool
361
    {
362
        return null !== $this->resettingToken;
363
    }
364
365
    public function requestPasswordReset()
366
    {
367
        $this->resettingToken = $this->generateToken();
368
        $this->passwordRequestedAt = new DateTime();
369
        $this->updated = new DateTime();
370
        $this->record(new PasswordResetRequested($this->id, $this->resettingToken));
371
    }
372
373
    public function resetPassword(string $resettingToken, string $password, UserPasswordEncoderInterface $encoder)
374
    {
375
        if (!$this->isPasswordResetRequested()) {
376
            throw new PasswordResettingNotRequestedException();
377
        }
378
379
        if ($resettingToken !== $this->resettingToken) {
380
            throw new InvalidPasswordResettingTokenException();
381
        }
382
383
        $this->resettingToken = null;
384
385
        $oldPassword = $this->password;
386
        $this->updatePassword($password, $encoder);
387
        $this->updated = new DateTime();
388
        $this->record(new PasswordReset($this->id, $oldPassword, $this->getPassword()));
389
390
        //We validate email if password has been confirmed because the token has been sent to the email adress.
391
        if (!$this->isEmailConfirmed()) {
392
            $this->confirmEmail($this->confirmationToken);
393
        }
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    private function updateCanonicalFields()
400
    {
401
        $this->usernameCanonical = self::canonicalize($this->username);
402
        $this->emailCanonical = self::canonicalize($this->email);
403
    }
404
405
    /**
406
     * @param string                       $plainPassword
407
     * @param UserPasswordEncoderInterface $encoder
408
     */
409
    private function updatePassword(string $plainPassword, UserPasswordEncoderInterface $encoder)
410
    {
411
        $this->password = $encoder->encodePassword($this, $plainPassword);
412
    }
413
414
    /**
415
     * Generate token string for salt, email validation or password resetting.
416
     *
417
     * @return string
418
     */
419
    private function generateToken(): string
420
    {
421
        return base_convert(sha1(uniqid((string) mt_rand(), true)), 16, 36);
422
    }
423
424
    protected function recordUserRegistered()
425
    {
426
        $this->record(new UserRegistered($this->getId(), $this->getEmail(), $this->getUsername()));
427
    }
428
429
    /**
430
     * @param $string
431
     *
432
     * @return string
433
     */
434
    public static function canonicalize(string $string): string
435
    {
436
        $trimmed = trim($string);
437
438
        return mb_convert_case($trimmed, MB_CASE_LOWER, mb_detect_encoding($string));
439
    }
440
441
    /**
442
     * @return string
443
     */
444
    public function getEmail(): string
445
    {
446
        return $this->email;
447
    }
448
449
    /**
450
     * Getter de lastLogin.
451
     *
452
     * @return DateTime
453
     */
454
    public function getLastLogin()
455
    {
456
        return $this->lastLogin;
457
    }
458
459
    /**
460
     * Getter de confirmationToken.
461
     *
462
     * @return string
463
     */
464
    public function getConfirmationToken()
465
    {
466
        return $this->confirmationToken;
467
    }
468
469
    /**
470
     * Getter de resettingToken.
471
     *
472
     * @return string
473
     */
474
    public function getResettingToken()
475
    {
476
        return $this->resettingToken;
477
    }
478
479
    /**
480
     * Getter de created.
481
     *
482
     * @return DateTime
483
     */
484
    public function getCreated()
485
    {
486
        return $this->created;
487
    }
488
489
    /**
490
     * Getter de updated.
491
     *
492
     * @return DateTime
493
     */
494
    public function getUpdated()
495
    {
496
        return $this->updated;
497
    }
498
}
499