Passed
Branch master (350f1b)
by Jan
04:53
created

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

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

776
        return in_array($code, /** @scrutinizer ignore-type */ $this->backupCodes, true);
Loading history...
777
    }
778
779
    /**
780
     * Invalidate a backup code.
781
     *
782
     * @param string $code The code that should be invalidated
783
     */
784
    public function invalidateBackupCode(string $code): void
785
    {
786
        $key = array_search($code, $this->backupCodes, true);
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

786
        $key = array_search($code, /** @scrutinizer ignore-type */ $this->backupCodes, true);
Loading history...
787
        if (false !== $key) {
788
            unset($this->backupCodes[$key]);
789
        }
790
    }
791
792
    /**
793
     * Returns the list of all valid backup codes.
794
     *
795
     * @return string[] An array with all backup codes
796
     */
797
    public function getBackupCodes(): array
798
    {
799
        return $this->backupCodes ?? [];
800
    }
801
802
    /**
803
     * Set the backup codes for this user. Existing backup codes are overridden.
804
     *
805
     * @param string[] $codes An array containing the backup codes
806
     *
807
     * @return $this
808
     *
809
     * @throws Exception If an error with the datetime occurs
810
     */
811
    public function setBackupCodes(array $codes): self
812
    {
813
        $this->backupCodes = $codes;
814
        if (empty($codes)) {
815
            $this->backupCodesGenerationDate = null;
816
        } else {
817
            $this->backupCodesGenerationDate = new DateTime();
818
        }
819
820
        return $this;
821
    }
822
823
    /**
824
     * Return the date when the backup codes were generated.
825
     *
826
     * @return DateTime|null
827
     */
828
    public function getBackupCodesGenerationDate(): ?DateTime
829
    {
830
        return $this->backupCodesGenerationDate;
831
    }
832
833
    /**
834
     * Return version for the trusted device token. Increase version to invalidate all trusted token of the user.
835
     *
836
     * @return int The version of trusted device token
837
     */
838
    public function getTrustedTokenVersion(): int
839
    {
840
        return $this->trustedDeviceCookieVersion;
841
    }
842
843
    /**
844
     * Invalidate all trusted device tokens at once, by incrementing the token version.
845
     * You have to flush the changes to database afterwards.
846
     */
847
    public function invalidateTrustedDeviceTokens(): void
848
    {
849
        ++$this->trustedDeviceCookieVersion;
850
    }
851
852
    /**
853
     * Check if U2F is enabled.
854
     *
855
     * @return bool
856
     */
857
    public function isU2FAuthEnabled(): bool
858
    {
859
        return count($this->u2fKeys) > 0;
860
    }
861
862
    /**
863
     *  Get all U2F Keys that are associated with this user.
864
     *
865
     * @return Collection
866
     *
867
     * @psalm-return Collection<int, TwoFactorKeyInterface>
868
     */
869
    public function getU2FKeys(): Collection
870
    {
871
        return $this->u2fKeys;
872
    }
873
874
    /**
875
     * Add a U2F key to this user.
876
     */
877
    public function addU2FKey(TwoFactorKeyInterface $key): void
878
    {
879
        $this->u2fKeys->add($key);
880
    }
881
882
    /**
883
     * Remove a U2F key from this user.
884
     */
885
    public function removeU2FKey(TwoFactorKeyInterface $key): void
886
    {
887
        $this->u2fKeys->removeElement($key);
888
    }
889
890
    public function getPreferredTwoFactorProvider(): ?string
891
    {
892
        //If U2F is available then prefer it
893
        if ($this->isU2FAuthEnabled()) {
894
            return 'u2f_two_factor';
895
        }
896
897
        //Otherwise use other methods
898
        return null;
899
    }
900
}
901