Passed
Push — master ( 23587f...1016f0 )
by Jan
04:38 queued 10s
created

User::getPwResetToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
20
 */
21
22
declare(strict_types=1);
23
24
/**
25
 * part-db version 0.1
26
 * Copyright (C) 2005 Christoph Lechner
27
 * http://www.cl-projects.de/.
28
 *
29
 * part-db version 0.2+
30
 * Copyright (C) 2009 K. Jacobs and others (see authors.php)
31
 * http://code.google.com/p/part-db/
32
 *
33
 * Part-DB Version 0.4+
34
 * Copyright (C) 2016 - 2019 Jan Böhmer
35
 * https://github.com/jbtronics
36
 *
37
 * This program is free software; you can redistribute it and/or
38
 * modify it under the terms of the GNU General Public License
39
 * as published by the Free Software Foundation; either version 2
40
 * of the License, or (at your option) any later version.
41
 *
42
 * This program is distributed in the hope that it will be useful,
43
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
44
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
45
 * GNU General Public License for more details.
46
 *
47
 * You should have received a copy of the GNU General Public License
48
 * along with this program; if not, write to the Free Software
49
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
50
 */
51
52
namespace App\Entity\UserSystem;
53
54
use App\Entity\Attachments\AttachmentContainingDBElement;
55
use App\Entity\Attachments\UserAttachment;
56
use App\Entity\Base\MasterAttachmentTrait;
57
use App\Entity\Base\NamedDBElement;
58
use App\Entity\PriceInformations\Currency;
59
use App\Security\Interfaces\HasPermissionsInterface;
60
use App\Validator\Constraints\Selectable;
61
use App\Validator\Constraints\ValidPermission;
62
use Doctrine\Common\Collections\ArrayCollection;
63
use Doctrine\Common\Collections\Collection;
64
use Doctrine\ORM\Mapping as ORM;
65
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
66
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
67
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
68
use Scheb\TwoFactorBundle\Model\PreferredProviderInterface;
69
use Scheb\TwoFactorBundle\Model\TrustedDeviceInterface;
70
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
71
use Symfony\Component\Security\Core\User\UserInterface;
72
use Symfony\Component\Validator\Constraints as Assert;
73
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface as U2FTwoFactorInterface;
74
75
/**
76
 * This entity represents a user, which can log in and have permissions.
77
 * Also this entity is able to save some informations about the user, like the names, email-address and other info.
78
 *
79
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
80
 * @ORM\Table("`users`")
81
 * @UniqueEntity("name", message="validator.user.username_already_used")
82
 */
83
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface,
84
    TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, U2FTwoFactorInterface, PreferredProviderInterface
85
{
86
    use MasterAttachmentTrait;
87
88
    /** The User id of the anonymous user */
89
    public const ID_ANONYMOUS = 1;
90
91
    public const AVAILABLE_THEMES = ['bootstrap', 'cerulean', 'cosmo', 'cyborg', 'darkly', 'flatly', 'journal',
92
        'litera', 'lumen', 'lux', 'materia', 'minty', 'pulse', 'sandstone', 'simplex', 'sketchy', 'slate', 'solar',
93
        'spacelab', 'united', 'yeti', ];
94
95
    /**
96
     * @var Collection|UserAttachment[]
97
     * @ORM\OneToMany(targetEntity="App\Entity\Attachments\UserAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
98
     */
99
    protected $attachments;
100
101
    /**
102
     * @ORM\Id()
103
     * @ORM\GeneratedValue()
104
     * @ORM\Column(type="integer")
105
     */
106
    protected $id;
107
108
    /**
109
     * @ORM\Column(type="string", length=180, unique=true)
110
     * @Assert\NotBlank
111
     */
112
    protected $name = '';
113
114
    /**
115
     * //@ORM\Column(type="json").
116
     */
117
    //protected $roles = [];
118
119
    /**
120
     * @var string|null The hashed password
121
     * @ORM\Column(type="string", nullable=true)
122
     */
123
    protected $password;
124
125
    /**
126
     * @var bool True if the user needs to change password after log in
127
     * @ORM\Column(type="boolean")
128
     */
129
    protected $need_pw_change = true;
130
131
    /**
132
     * @var string|null The first name of the User
133
     * @ORM\Column(type="string", length=255, nullable=true)
134
     */
135
    protected $first_name = '';
136
137
    /**
138
     * @var string|null The last name of the User
139
     * @ORM\Column(type="string", length=255,  nullable=true)
140
     */
141
    protected $last_name = '';
142
143
    /**
144
     * @var string|null The department the user is working
145
     * @ORM\Column(type="string", length=255, nullable=true)
146
     */
147
    protected $department = '';
148
149
    /**
150
     * @var string|null The email address of the user
151
     * @ORM\Column(type="string", length=255, nullable=true)
152
     * @Assert\Email()
153
     */
154
    protected $email = '';
155
156
    /**
157
     * @var string|null The language/locale the user prefers
158
     * @ORM\Column(type="string", name="config_language", nullable=true)
159
     * @Assert\Language()
160
     */
161
    protected $language = '';
162
163
    /**
164
     * @var string|null The timezone the user prefers
165
     * @ORM\Column(type="string", name="config_timezone", nullable=true)
166
     * @Assert\Timezone()
167
     */
168
    protected $timezone = '';
169
170
    /**
171
     * @var string|null The theme
172
     * @ORM\Column(type="string", name="config_theme", nullable=true)
173
     * @Assert\Choice(choices=User::AVAILABLE_THEMES)
174
     */
175
    protected $theme = '';
176
177
    /**
178
     * @var Group|null the group this user belongs to
179
     * @ORM\ManyToOne(targetEntity="Group", inversedBy="users", fetch="EAGER")
180
     * @ORM\JoinColumn(name="group_id", referencedColumnName="id")
181
     * @Selectable()
182
     */
183
    protected $group;
184
185
    /**
186
     * @var string|null The secret used for google authenticator
187
     * @ORM\Column(name="google_authenticator_secret", type="string", nullable=true)
188
     */
189
    protected $googleAuthenticatorSecret;
190
191
    /**
192
     * @var string[]|null A list of backup codes that can be used, if the user has no access to its Google Authenticator device
193
     * @ORM\Column(type="json")
194
     */
195
    protected $backupCodes = [];
196
197
    /** @var \DateTime The time when the backup codes were generated
198
     * @ORM\Column(type="datetime", nullable=true)
199
     */
200
    protected $backupCodesGenerationDate;
201
202
    /** @var int The version of the trusted device cookie. Used to invalidate all trusted device cookies at once.
203
     *  @ORM\Column(type="integer")
204
     */
205
    protected $trustedDeviceCookieVersion = 0;
206
207
    /** @var Collection<TwoFactorKeyInterface>
208
      * @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
209
      */
210
    protected $u2fKeys;
211
212
    /**
213
     * @var array
214
     * @ORM\Column(type="json")
215
     */
216
    protected $settings = [];
217
218
    /**
219
     * @var Currency|null The currency the user wants to see prices in.
220
     *                    Dont use fetch=EAGER here, this will cause problems with setting the currency setting.
221
     *                    TODO: This is most likely a bug in doctrine/symfony related to the UniqueEntity constraint (it makes a db call).
222
     *                    TODO: Find a way to use fetch EAGER (this improves performance a bit)
223
     * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency")
224
     * @ORM\JoinColumn(name="currency_id", referencedColumnName="id")
225
     * @Selectable()
226
     */
227
    protected $currency = null;
228
229
    /** @var PermissionsEmbed
230
     * @ORM\Embedded(class="PermissionsEmbed", columnPrefix="perms_")
231
     * @ValidPermission()
232
     */
233
    protected $permissions;
234
235
    /**
236
     * @ORM\Column(type="text", name="config_instock_comment_w")
237
     */
238
    protected $instock_comment_w = '';
239
240
    /**
241
     * @ORM\Column(type="text", name="config_instock_comment_a")
242
     */
243
    protected $instock_comment_a = '';
244
245
    /**
246
     * @var string|null The hash of a token the user must provide when he wants to reset his password.
247
     * @ORM\Column(type="string", nullable=true)
248
     */
249
    protected $pw_reset_token = null;
250
251
    /**
252
     * @var \DateTime The time until the password reset token is valid.
253
     * @ORM\Column(type="datetime", nullable=true)
254
     */
255
    protected $pw_reset_expires = null;
256
257
    /**
258
     * @var bool Determines if the user is disabled (user can not log in)
259
     * @ORM\Column(type="boolean")
260
     */
261
    protected $disabled = false;
262
263
    public function __construct()
264
    {
265
        parent::__construct();
266
        $this->permissions = new PermissionsEmbed();
267
        $this->u2fKeys = new ArrayCollection();
268
    }
269
270
    /**
271
     * Checks if the current user, is the user which represents the not logged in (anonymous) users.
272
     *
273
     * @return bool true if this user is the anonymous user
274
     */
275
    public function isAnonymousUser(): bool
276
    {
277
        return $this->id === static::ID_ANONYMOUS && 'anonymous' === $this->name;
278
    }
279
280
    /**
281
     * A visual identifier that represents this user.
282
     *
283
     * @see UserInterface
284
     */
285
    public function getUsername(): string
286
    {
287
        return (string) $this->name;
288
    }
289
290
    /**
291
     * @see UserInterface
292
     */
293
    public function getRoles(): array
294
    {
295
        $roles = [];
296
        //$roles = $this->roles;
297
        // guarantee every user at least has ROLE_USER
298
        $roles[] = 'ROLE_USER';
299
300
        return array_unique($roles);
301
    }
302
303
    public function setRoles(array $roles): self
0 ignored issues
show
Unused Code introduced by
The parameter $roles is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

303
    public function setRoles(/** @scrutinizer ignore-unused */ array $roles): self

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
304
    {
305
        //$this->roles = $roles;
306
307
        return $this;
308
    }
309
310
    /**
311
     * @see UserInterface
312
     * Gets the password hash for this entity.
313
     */
314
    public function getPassword(): string
315
    {
316
        return (string) $this->password;
317
    }
318
319
    /**
320
     * Sets the password hash for this user.
321
     *
322
     * @param string $password
323
     * @return User
324
     */
325
    public function setPassword(string $password): self
326
    {
327
        $this->password = $password;
328
329
        return $this;
330
    }
331
332
    /**
333
     * @see UserInterface
334
     */
335
    public function getSalt()
336
    {
337
        // not needed when using the "bcrypt" algorithm in security.yaml
338
    }
339
340
    /**
341
     * @see UserInterface
342
     */
343
    public function eraseCredentials()
344
    {
345
        // If you store any temporary, sensitive data on the user, clear it here
346
        // $this->plainPassword = null;
347
    }
348
349
    /**
350
     * Gets the currency the user prefers when showing him prices.
351
     *
352
     * @return Currency|null The currency the user prefers, or null if the global currency should be used.
353
     */
354
    public function getCurrency(): ?Currency
355
    {
356
        return $this->currency;
357
    }
358
359
    /**
360
     * Sets the currency the users prefers to see prices in.
361
     *
362
     * @param Currency|null $currency
363
     * @return User
364
     */
365
    public function setCurrency(?Currency $currency): self
366
    {
367
        $this->currency = $currency;
368
369
        return $this;
370
    }
371
372
    /**
373
     * Checks if this user is disabled (user cannot login any more).
374
     *
375
     * @return bool True, if the user is disabled.
376
     */
377
    public function isDisabled(): bool
378
    {
379
        return $this->disabled;
380
    }
381
382
    /**
383
     * Sets the status if a user is disabled.
384
     *
385
     * @param bool $disabled True if the user should be disabled.
386
     *
387
     * @return User
388
     */
389
    public function setDisabled(bool $disabled): self
390
    {
391
        $this->disabled = $disabled;
392
393
        return $this;
394
    }
395
396
    /**
397
     * Returns the ID as an string, defined by the element class.
398
     * This should have a form like P000014, for a part with ID 14.
399
     *
400
     * @return string The ID as a string;
401
     */
402
    public function getIDString(): string
403
    {
404
        return 'U'.sprintf('%06d', $this->getID());
405
    }
406
407
    public function getPermissions(): PermissionsEmbed
408
    {
409
        return $this->permissions;
410
    }
411
412
    /**
413
     * Check if the user needs a password change.
414
     *
415
     * @return bool
416
     */
417
    public function isNeedPwChange(): bool
418
    {
419
        return $this->need_pw_change;
420
    }
421
422
    /**
423
     * Set the status, if the user needs a password change.
424
     *
425
     * @param bool $need_pw_change
426
     * @return User
427
     */
428
    public function setNeedPwChange(bool $need_pw_change): self
429
    {
430
        $this->need_pw_change = $need_pw_change;
431
432
        return $this;
433
    }
434
435
    /**
436
     * Returns the encrypted password reset token
437
     * @return string|null
438
     */
439
    public function getPwResetToken(): ?string
440
    {
441
        return $this->pw_reset_token;
442
    }
443
444
    /**
445
     * Sets the encrypted password reset token
446
     * @param string|null $pw_reset_token
447
     * @return User
448
     */
449
    public function setPwResetToken(?string $pw_reset_token): User
450
    {
451
        $this->pw_reset_token = $pw_reset_token;
452
        return $this;
453
    }
454
455
    /**
456
     * Gets the datetime when the password reset token expires
457
     * @return \DateTime
458
     */
459
    public function getPwResetExpires(): \DateTime
460
    {
461
        return $this->pw_reset_expires;
462
    }
463
464
    /**
465
     * Sets the datetime when the password reset token expires
466
     * @param \DateTime $pw_reset_expires
467
     * @return User
468
     */
469
    public function setPwResetExpires(\DateTime $pw_reset_expires): User
470
    {
471
        $this->pw_reset_expires = $pw_reset_expires;
472
        return $this;
473
    }
474
475
476
477
    /************************************************
478
     * Getters
479
     ************************************************/
480
481
    /**
482
     * Returns the full name in the format FIRSTNAME LASTNAME [(USERNAME)].
483
     * Example: Max Muster (m.muster).
484
     *
485
     * @param bool $including_username include the username in the full name
486
     *
487
     * @return string a string with the full name of this user
488
     */
489
    public function getFullName(bool $including_username = false): string
490
    {
491
        if ($including_username) {
492
            return sprintf('%s %s (%s)', $this->getFirstName(), $this->getLastName(), $this->getName());
493
        }
494
495
        return sprintf('%s %s', $this->getFirstName(), $this->getLastName());
496
    }
497
498
    /**
499
     * Change the username of this user
500
     * @param string $new_name The new username.
501
     * @return $this
502
     */
503
    public function setName(string $new_name): NamedDBElement
504
    {
505
        // Anonymous user is not allowed to change its username
506
        if (!$this->isAnonymousUser()) {
507
            $this->name = $new_name;
508
        }
509
510
        return $this;
511
    }
512
513
    /**
514
     * Get the first name of the user.
515
     * @return string|null
516
     */
517
    public function getFirstName(): ?string
518
    {
519
        return $this->first_name;
520
    }
521
522
    /**
523
     * Change the first name of the user
524
     * @param string $first_name The new first name
525
     *
526
     * @return $this
527
     */
528
    public function setFirstName(?string $first_name): self
529
    {
530
        $this->first_name = $first_name;
531
532
        return $this;
533
    }
534
535
    /**
536
     * Get the last name of the user
537
     * @return string|null
538
     */
539
    public function getLastName(): ?string
540
    {
541
        return $this->last_name;
542
    }
543
544
    /**
545
     * Change the last name of the user
546
     * @param string $last_name The new last name
547
     *
548
     * @return $this
549
     */
550
    public function setLastName(?string $last_name): self
551
    {
552
        $this->last_name = $last_name;
553
554
        return $this;
555
    }
556
557
    /**
558
     * Gets the department of this user
559
     * @return string
560
     */
561
    public function getDepartment(): ?string
562
    {
563
        return $this->department;
564
    }
565
566
    /**
567
     * Change the department of the user
568
     * @param string $department The new department
569
     * @return User
570
     */
571
    public function setDepartment(?string $department): self
572
    {
573
        $this->department = $department;
574
575
        return $this;
576
    }
577
578
    /**
579
     * Get the email of the user.
580
     * @return string
581
     */
582
    public function getEmail(): ?string
583
    {
584
        return $this->email;
585
    }
586
587
    /**
588
     * Change the email of the user
589
     * @param string $email The new email adress
590
     * @return $this
591
     */
592
    public function setEmail(?string $email): self
593
    {
594
        $this->email = $email;
595
596
        return $this;
597
    }
598
599
    /**
600
     * Gets the language the user prefers (as 2 letter ISO code).
601
     * @return string|null The 2 letter ISO code of the preferred language (e.g. 'en' or 'de').
602
     *  If null is returned, the user has not specified a language and the server wide language should be used.
603
     */
604
    public function getLanguage(): ?string
605
    {
606
        return $this->language;
607
    }
608
609
    /**
610
     * Change the language the user prefers.
611
     * @param string|null $language The new language as 2 letter ISO code (e.g. 'en' or 'de').
612
     * Set to null, to use the system wide language.
613
     * @return User
614
     */
615
    public function setLanguage(?string $language): self
616
    {
617
        $this->language = $language;
618
        return $this;
619
    }
620
621
    /**
622
     * Gets the timezone of the user
623
     * @return string|null The timezone of the user (e.g. 'Europe/Berlin') or null if the user has not specified
624
     * a timezone (then the global one should be used)
625
     */
626
    public function getTimezone(): ?string
627
    {
628
        return $this->timezone;
629
    }
630
631
    /**
632
     * Change the timezone of this user.
633
     * @param string $timezone|null The new timezone (e.g. 'Europe/Berlin') or null to use the system wide one.
634
     * @return $this
635
     */
636
    public function setTimezone(?string $timezone): self
637
    {
638
        $this->timezone = $timezone;
639
640
        return $this;
641
    }
642
643
    /**
644
     * Gets the theme the users wants to see. See self::AVAILABLE_THEMES for valid values.
645
     * @return string|null The name of the theme the user wants to see, or null if the system wide should be used.
646
     */
647
    public function getTheme(): ?string
648
    {
649
        return $this->theme;
650
    }
651
652
    /**
653
     * Change the theme the user wants to see.
654
     * @param string|null $theme The name of the theme (See See self::AVAILABLE_THEMES for valid values). Set to null
655
     * if the system wide theme should be used.
656
     * @return $this
657
     */
658
    public function setTheme(?string $theme): self
659
    {
660
        $this->theme = $theme;
661
662
        return $this;
663
    }
664
665
    /**
666
     * Gets the group to which this user belongs to.
667
     * @return Group|null The group of this user. Null if this user does not have a group.
668
     */
669
    public function getGroup(): ?Group
670
    {
671
        return $this->group;
672
    }
673
674
    /**
675
     * Sets the group of this user.
676
     * @param Group|null $group The new group of this user. Set to null if this user should not have a group.
677
     * @return $this
678
     */
679
    public function setGroup(?Group $group): self
680
    {
681
        $this->group = $group;
682
683
        return $this;
684
    }
685
686
    /**
687
     * Returns a string representation of this user (the full name).
688
     * E.g. 'Jane Doe (j.doe) [DISABLED]
689
     * @return string
690
     */
691
    public function __toString()
692
    {
693
        $tmp = $this->isDisabled() ? ' [DISABLED]' : '';
694
        return $this->getFullName(true).$tmp;
695
    }
696
697
    /**
698
     * Return true if the user should do two-factor authentication.
699
     *
700
     * @return bool
701
     */
702
    public function isGoogleAuthenticatorEnabled(): bool
703
    {
704
        return $this->googleAuthenticatorSecret ? true : false;
705
    }
706
707
    /**
708
     * Return the user name that should be shown in Google Authenticator.
709
     * @return string
710
     */
711
    public function getGoogleAuthenticatorUsername(): string
712
    {
713
        return $this->getUsername();
714
    }
715
716
    /**
717
     * Return the Google Authenticator secret
718
     * When an empty string is returned, the Google authentication is disabled.
719
     *
720
     * @return string|null
721
     */
722
    public function getGoogleAuthenticatorSecret(): ?string
723
    {
724
        return $this->googleAuthenticatorSecret;
725
    }
726
727
    /**
728
     * Sets the secret used for Google Authenticator. Set to null to disable Google Authenticator.
729
     * @param string|null $googleAuthenticatorSecret
730
     * @return $this
731
     */
732
    public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): self
733
    {
734
        $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
735
        return $this;
736
    }
737
738
    /**
739
     * Check if the given code is a valid backup code.
740
     *
741
     * @param string $code The code that should be checked.
742
     * @return bool True if the backup code is valid.
743
     */
744
    public function isBackupCode(string $code): bool
745
    {
746
        return in_array($code, $this->backupCodes);
0 ignored issues
show
Bug introduced by
It seems like $this->backupCodes can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

746
        return in_array($code, /** @scrutinizer ignore-type */ $this->backupCodes);
Loading history...
747
    }
748
749
    /**
750
     * Invalidate a backup code.
751
     *
752
     * @param string $code The code that should be invalidated
753
     */
754
    public function invalidateBackupCode(string $code): void
755
    {
756
        $key = array_search($code, $this->backupCodes);
0 ignored issues
show
Bug introduced by
It seems like $this->backupCodes can also be of type null; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

756
        $key = array_search($code, /** @scrutinizer ignore-type */ $this->backupCodes);
Loading history...
757
        if ($key !== false){
758
            unset($this->backupCodes[$key]);
759
        }
760
    }
761
762
    /**
763
     * Returns the list of all valid backup codes
764
     * @return string[] An array with all backup codes
765
     */
766
    public function getBackupCodes() : array
767
    {
768
        return $this->backupCodes ?? [];
769
    }
770
771
    /**
772
     * Set the backup codes for this user. Existing backup codes are overridden.
773
     * @param string[] $codes A
774
     * @return $this
775
     */
776
    public function setBackupCodes(array $codes) : self
777
    {
778
        $this->backupCodes = $codes;
779
        if(empty($codes)) {
780
            $this->backupCodesGenerationDate = null;
781
        } else {
782
            $this->backupCodesGenerationDate = new \DateTime();
783
        }
784
        return $this;
785
    }
786
787
    /**
788
     * Return the date when the backup codes were generated.
789
     * @return \DateTime|null
790
     */
791
    public function getBackupCodesGenerationDate() : ?\DateTime
792
    {
793
        return $this->backupCodesGenerationDate;
794
    }
795
796
    /**
797
     * Return version for the trusted device token. Increase version to invalidate all trusted token of the user.
798
     * @return int The version of trusted device token
799
     */
800
    public function getTrustedTokenVersion(): int
801
    {
802
        return $this->trustedDeviceCookieVersion;
803
    }
804
805
    /**
806
     * Invalidate all trusted device tokens at once, by incrementing the token version.
807
     * You have to flush the changes to database afterwards.
808
     */
809
    public function invalidateTrustedDeviceTokens() : void
810
    {
811
        $this->trustedDeviceCookieVersion++;
812
    }
813
814
    /**
815
     * Check if U2F is enabled
816
     * @return bool
817
     */
818
    public function isU2FAuthEnabled(): bool
819
    {
820
        return count($this->u2fKeys) > 0;
821
    }
822
823
    /**
824
     * Get all U2F Keys that are associated with this user
825
     * @return Collection<TwoFactorKeyInterface>
826
     */
827
    public function getU2FKeys(): Collection
828
    {
829
        return $this->u2fKeys;
830
    }
831
832
    /**
833
     * Add a U2F key to this user.
834
     * @param  TwoFactorKeyInterface  $key
835
     */
836
    public function addU2FKey(TwoFactorKeyInterface $key): void
837
    {
838
        $this->u2fKeys->add($key);
839
    }
840
841
    /**
842
     * Remove a U2F key from this user.
843
     * @param  TwoFactorKeyInterface  $key
844
     */
845
    public function removeU2FKey(TwoFactorKeyInterface $key): void
846
    {
847
        $this->u2fKeys->removeElement($key);
848
    }
849
850
    /**
851
     * @inheritDoc
852
     */
853
    public function getPreferredTwoFactorProvider(): ?string
854
    {
855
        //If U2F is available then prefer it
856
        if($this->isU2FAuthEnabled()) {
857
            return 'u2f_two_factor';
858
        }
859
860
        //Otherwise use other methods
861
        return null;
862
    }
863
}
864