User::getLanguage()   A
last analyzed

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 - 2022 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
namespace App\Entity\UserSystem;
24
25
use App\Entity\Attachments\AttachmentContainingDBElement;
26
use App\Entity\Attachments\UserAttachment;
27
use App\Entity\Base\AbstractNamedDBElement;
28
use App\Entity\PriceInformations\Currency;
29
use App\Security\Interfaces\HasPermissionsInterface;
30
use App\Validator\Constraints\Selectable;
31
use App\Validator\Constraints\ValidPermission;
32
use App\Validator\Constraints\ValidTheme;
33
use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface;
34
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
35
use Webauthn\PublicKeyCredentialUserEntity;
36
use function count;
37
use DateTime;
38
use Doctrine\Common\Collections\ArrayCollection;
39
use Doctrine\Common\Collections\Collection;
40
use Doctrine\ORM\Mapping as ORM;
41
use Exception;
42
use function in_array;
43
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
44
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
45
use Scheb\TwoFactorBundle\Model\PreferredProviderInterface;
46
use Scheb\TwoFactorBundle\Model\TrustedDeviceInterface;
47
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
48
use Symfony\Component\Security\Core\User\UserInterface;
49
use Symfony\Component\Validator\Constraints as Assert;
50
use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface;
51
52
/**
53
 * This entity represents a user, which can log in and have permissions.
54
 * Also this entity is able to save some informations about the user, like the names, email-address and other info.
55
 *
56
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
57
 * @ORM\Table("`users`", indexes={
58
 *    @ORM\Index(name="user_idx_username", columns={"name"})
59
 * })
60
 * @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"})
61
 * @UniqueEntity("name", message="validator.user.username_already_used")
62
 */
63
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface
64
{
65
    //use MasterAttachmentTrait;
66
67
    /**
68
     * The User id of the anonymous user.
69
     */
70
    public const ID_ANONYMOUS = 1;
71
72
    /**
73
     * @var bool Determines if the user is disabled (user can not log in)
74
     * @ORM\Column(type="boolean")
75
     */
76
    protected bool $disabled = false;
77
78
    /**
79
     * @var string|null The theme
80
     * @ORM\Column(type="string", name="config_theme", nullable=true)
81
     * @ValidTheme()
82
     */
83
    protected ?string $theme = null;
84
85
    /**
86
     * @var string|null the hash of a token the user must provide when he wants to reset his password
87
     * @ORM\Column(type="string", nullable=true)
88
     */
89
    protected ?string $pw_reset_token = null;
90
91
    /**
92
     * @ORM\Column(type="text", name="config_instock_comment_a")
93
     */
94
    protected string $instock_comment_a = '';
95
96
    /**
97
     * @ORM\Column(type="text", name="config_instock_comment_w")
98
     */
99
    protected string $instock_comment_w = '';
100
101
    /** @var int The version of the trusted device cookie. Used to invalidate all trusted device cookies at once.
102
     *  @ORM\Column(type="integer")
103
     */
104
    protected int $trustedDeviceCookieVersion = 0;
105
106
    /**
107
     * @var string[]|null A list of backup codes that can be used, if the user has no access to its Google Authenticator device
108
     * @ORM\Column(type="json")
109
     */
110
    protected ?array $backupCodes = [];
111
112
    /**
113
     * @ORM\Id()
114
     * @ORM\GeneratedValue()
115
     * @ORM\Column(type="integer")
116
     */
117
    protected ?int $id = null;
118
119
    /**
120
     * @var Group|null the group this user belongs to
121
     * DO NOT PUT A fetch eager here! Otherwise you can not unset the group of a user! This seems to be some kind of bug in doctrine. Maybe this is fixed in future versions.
122
     * @ORM\ManyToOne(targetEntity="Group", inversedBy="users")
123
     * @ORM\JoinColumn(name="group_id", referencedColumnName="id")
124
     * @Selectable()
125
     */
126
    protected ?Group $group = null;
127
128
    /**
129
     * @var string|null The secret used for google authenticator
130
     * @ORM\Column(name="google_authenticator_secret", type="string", nullable=true)
131
     */
132
    protected ?string $googleAuthenticatorSecret = null;
133
134
    /**
135
     * @var string|null The timezone the user prefers
136
     * @ORM\Column(type="string", name="config_timezone", nullable=true)
137
     * @Assert\Timezone()
138
     */
139
    protected ?string $timezone = '';
140
141
    /**
142
     * @var string|null The language/locale the user prefers
143
     * @ORM\Column(type="string", name="config_language", nullable=true)
144
     * @Assert\Language()
145
     */
146
    protected ?string $language = '';
147
148
    /**
149
     * @var string|null The email address of the user
150
     * @ORM\Column(type="string", length=255, nullable=true)
151
     * @Assert\Email()
152
     */
153
    protected ?string $email = '';
154
155
    /**
156
     * @var string|null The department the user is working
157
     * @ORM\Column(type="string", length=255, nullable=true)
158
     */
159
    protected ?string $department = '';
160
161
    /**
162
     * @var string|null The last name of the User
163
     * @ORM\Column(type="string", length=255,  nullable=true)
164
     */
165
    protected ?string $last_name = '';
166
167
    /**
168
     * @var string|null The first name of the User
169
     * @ORM\Column(type="string", length=255, nullable=true)
170
     */
171
    protected ?string $first_name = '';
172
173
    /**
174
     * @var bool True if the user needs to change password after log in
175
     * @ORM\Column(type="boolean")
176
     */
177
    protected bool $need_pw_change = true;
178
179
    /**
180
     * @var string|null The hashed password
181
     * @ORM\Column(type="string", nullable=true)
182
     */
183
    protected ?string $password = null;
184
185
    /**
186
     * @ORM\Column(type="string", length=180, unique=true)
187
     * @Assert\NotBlank
188
     * @Assert\Regex("/^[\w\.\+\-\$]+$/", message="user.invalid_username")
189
     */
190
    protected string $name = '';
191
192
    /**
193
     * @var array
194
     * @ORM\Column(type="json")
195
     */
196
    protected ?array $settings = [];
197
198
    /**
199
     * @var Collection<int, UserAttachment>
200
     * @ORM\OneToMany(targetEntity="App\Entity\Attachments\UserAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
201
     * @ORM\OrderBy({"name" = "ASC"})
202
     */
203
    protected $attachments;
204
205
    /** @var DateTime|null The time when the backup codes were generated
206
     * @ORM\Column(type="datetime", nullable=true)
207
     */
208
    protected ?DateTime $backupCodesGenerationDate = null;
209
210
    /** @var Collection<int, LegacyU2FKeyInterface>
211
     * @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
212
     */
213
    protected $u2fKeys;
214
215
    /**
216
     * @var Collection<int, WebauthnKey>
217
     * @ORM\OneToMany(targetEntity="App\Entity\UserSystem\WebauthnKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
218
     */
219
    protected $webauthn_keys;
220
221
    /**
222
     * @var Currency|null The currency the user wants to see prices in.
223
     *                    Dont use fetch=EAGER here, this will cause problems with setting the currency setting.
224
     *                    TODO: This is most likely a bug in doctrine/symfony related to the UniqueEntity constraint (it makes a db call).
225
     *                    TODO: Find a way to use fetch EAGER (this improves performance a bit)
226
     * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency")
227
     * @ORM\JoinColumn(name="currency_id", referencedColumnName="id")
228
     * @Selectable()
229
     */
230
    protected $currency;
231
232
    /**
233
     * @var PermissionData
234
     * @ValidPermission()
235
     * @ORM\Embedded(class="PermissionData", columnPrefix="permissions_")
236
     */
237
    protected ?PermissionData $permissions = null;
238
239
    /**
240
     * @var DateTime the time until the password reset token is valid
241
     * @ORM\Column(type="datetime", nullable=true)
242
     */
243
    protected $pw_reset_expires;
244
245
    public function __construct()
246
    {
247
        parent::__construct();
248
        $this->permissions = new PermissionData();
249
        $this->u2fKeys = new ArrayCollection();
250
        $this->webauthn_keys = new ArrayCollection();
251
    }
252
253
    /**
254
     * Returns a string representation of this user (the full name).
255
     * E.g. 'Jane Doe (j.doe) [DISABLED].
256
     *
257
     * @return string
258
     */
259
    public function __toString()
260
    {
261
        $tmp = $this->isDisabled() ? ' [DISABLED]' : '';
262
263
        return $this->getFullName(true).$tmp;
264
    }
265
266
    /**
267
     * Checks if the current user, is the user which represents the not logged in (anonymous) users.
268
     *
269
     * @return bool true if this user is the anonymous user
270
     */
271
    public function isAnonymousUser(): bool
272
    {
273
        return $this->id === static::ID_ANONYMOUS && 'anonymous' === $this->name;
274
    }
275
276
    /**
277
     * A visual identifier that represents this user.
278
     *
279
     * @see UserInterface
280
     */
281
    public function getUsername(): string
282
    {
283
        return $this->name;
284
    }
285
286
    public function getUserIdentifier(): string
287
    {
288
        return $this->getUsername();
289
    }
290
291
    /**
292
     * @see UserInterface
293
     */
294
    public function getRoles(): array
295
    {
296
        $roles = [];
297
        //$roles = $this->roles;
298
        // guarantee every user at least has ROLE_USER
299
        $roles[] = 'ROLE_USER';
300
301
        return array_unique($roles);
302
    }
303
304
    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

304
    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...
305
    {
306
        //$this->roles = $roles;
307
308
        return $this;
309
    }
310
311
    /**
312
     * @see UserInterface
313
     * Gets the password hash for this entity.
314
     */
315
    public function getPassword(): string
316
    {
317
        return (string) $this->password;
318
    }
319
320
    /**
321
     * Sets the password hash for this user.
322
     *
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(): ?string
336
    {
337
        return null;
338
    }
339
340
    /**
341
     * @see UserInterface
342
     */
343
    public function eraseCredentials(): void
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
     * @return User
363
     */
364
    public function setCurrency(?Currency $currency): self
365
    {
366
        $this->currency = $currency;
367
368
        return $this;
369
    }
370
371
    /**
372
     * Checks if this user is disabled (user cannot login any more).
373
     *
374
     * @return bool true, if the user is disabled
375
     */
376
    public function isDisabled(): bool
377
    {
378
        return $this->disabled;
379
    }
380
381
    /**
382
     * Sets the status if a user is disabled.
383
     *
384
     * @param bool $disabled true if the user should be disabled
385
     *
386
     * @return User
387
     */
388
    public function setDisabled(bool $disabled): self
389
    {
390
        $this->disabled = $disabled;
391
392
        return $this;
393
    }
394
395
    public function getPermissions(): PermissionData
396
    {
397
        if ($this->permissions === null) {
398
            $this->permissions = new PermissionData();
399
        }
400
401
        return $this->permissions;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->permissions could return the type null which is incompatible with the type-hinted return App\Entity\UserSystem\PermissionData. Consider adding an additional type-check to rule them out.
Loading history...
402
    }
403
404
    /**
405
     * Check if the user needs a password change.
406
     */
407
    public function isNeedPwChange(): bool
408
    {
409
        return $this->need_pw_change;
410
    }
411
412
    /**
413
     * Set the status, if the user needs a password change.
414
     *
415
     * @return User
416
     */
417
    public function setNeedPwChange(bool $need_pw_change): self
418
    {
419
        $this->need_pw_change = $need_pw_change;
420
421
        return $this;
422
    }
423
424
    /**
425
     * Returns the encrypted password reset token.
426
     */
427
    public function getPwResetToken(): ?string
428
    {
429
        return $this->pw_reset_token;
430
    }
431
432
    /**
433
     * Sets the encrypted password reset token.
434
     *
435
     * @return User
436
     */
437
    public function setPwResetToken(?string $pw_reset_token): self
438
    {
439
        $this->pw_reset_token = $pw_reset_token;
440
441
        return $this;
442
    }
443
444
    /**
445
     * Gets the datetime when the password reset token expires.
446
     */
447
    public function getPwResetExpires(): DateTime
448
    {
449
        return $this->pw_reset_expires;
450
    }
451
452
    /**
453
     * Sets the datetime when the password reset token expires.
454
     *
455
     * @return User
456
     */
457
    public function setPwResetExpires(DateTime $pw_reset_expires): self
458
    {
459
        $this->pw_reset_expires = $pw_reset_expires;
460
461
        return $this;
462
    }
463
464
    /************************************************
465
     * Getters
466
     ************************************************/
467
468
    /**
469
     * Returns the full name in the format FIRSTNAME LASTNAME [(USERNAME)].
470
     * Example: Max Muster (m.muster).
471
     *
472
     * @param bool $including_username include the username in the full name
473
     *
474
     * @return string a string with the full name of this user
475
     */
476
    public function getFullName(bool $including_username = false): string
477
    {
478
        $tmp = $this->getFirstName();
479
        //Dont add a space, if the name has only one part (it would look strange)
480
        if (!empty($this->getFirstName()) && !empty($this->getLastName())) {
481
            $tmp .= ' ';
482
        }
483
        $tmp .= $this->getLastName();
484
485
        if ($including_username) {
486
            $tmp .= sprintf(' (@%s)', $this->getName());
487
        }
488
489
        return $tmp;
490
    }
491
492
    /**
493
     * Change the username of this user.
494
     *
495
     * @param string $new_name the new username
496
     *
497
     * @return $this
498
     */
499
    public function setName(string $new_name): AbstractNamedDBElement
500
    {
501
        // Anonymous user is not allowed to change its username
502
        if (!$this->isAnonymousUser()) {
503
            $this->name = $new_name;
504
        }
505
506
        return $this;
507
    }
508
509
    /**
510
     * Get the first name of the user.
511
     */
512
    public function getFirstName(): ?string
513
    {
514
        return $this->first_name;
515
    }
516
517
    /**
518
     * Change the first name of the user.
519
     *
520
     * @param  string|null  $first_name  The new first name
521
     *
522
     * @return $this
523
     */
524
    public function setFirstName(?string $first_name): self
525
    {
526
        $this->first_name = $first_name;
527
528
        return $this;
529
    }
530
531
    /**
532
     * Get the last name of the user.
533
     */
534
    public function getLastName(): ?string
535
    {
536
        return $this->last_name;
537
    }
538
539
    /**
540
     * Change the last name of the user.
541
     *
542
     * @param  string|null  $last_name  The new last name
543
     *
544
     * @return $this
545
     */
546
    public function setLastName(?string $last_name): self
547
    {
548
        $this->last_name = $last_name;
549
550
        return $this;
551
    }
552
553
    /**
554
     * Gets the department of this user.
555
     *
556
     * @return string
557
     */
558
    public function getDepartment(): ?string
559
    {
560
        return $this->department;
561
    }
562
563
    /**
564
     * Change the department of the user.
565
     *
566
     * @param  string|null  $department  The new department
567
     *
568
     * @return User
569
     */
570
    public function setDepartment(?string $department): self
571
    {
572
        $this->department = $department;
573
574
        return $this;
575
    }
576
577
    /**
578
     * Get the email of the user.
579
     *
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
     *
590
     * @param  string|null  $email  The new email adress
591
     *
592
     * @return $this
593
     */
594
    public function setEmail(?string $email): self
595
    {
596
        $this->email = $email;
597
598
        return $this;
599
    }
600
601
    /**
602
     * Gets the language the user prefers (as 2 letter ISO code).
603
     *
604
     * @return string|null The 2 letter ISO code of the preferred language (e.g. 'en' or 'de').
605
     *                     If null is returned, the user has not specified a language and the server wide language should be used.
606
     */
607
    public function getLanguage(): ?string
608
    {
609
        return $this->language;
610
    }
611
612
    /**
613
     * Change the language the user prefers.
614
     *
615
     * @param string|null $language The new language as 2 letter ISO code (e.g. 'en' or 'de').
616
     *                              Set to null, to use the system wide language.
617
     *
618
     * @return User
619
     */
620
    public function setLanguage(?string $language): self
621
    {
622
        $this->language = $language;
623
624
        return $this;
625
    }
626
627
    /**
628
     * Gets the timezone of the user.
629
     *
630
     * @return string|null The timezone of the user (e.g. 'Europe/Berlin') or null if the user has not specified
631
     *                     a timezone (then the global one should be used)
632
     */
633
    public function getTimezone(): ?string
634
    {
635
        return $this->timezone;
636
    }
637
638
    /**
639
     * Change the timezone of this user.
640
     *
641
     * @return $this
642
     */
643
    public function setTimezone(?string $timezone): self
644
    {
645
        $this->timezone = $timezone;
646
647
        return $this;
648
    }
649
650
    /**
651
     * Gets the theme the users wants to see. See self::AVAILABLE_THEMES for valid values.
652
     *
653
     * @return string|null the name of the theme the user wants to see, or null if the system wide should be used
654
     */
655
    public function getTheme(): ?string
656
    {
657
        return $this->theme;
658
    }
659
660
    /**
661
     * Change the theme the user wants to see.
662
     *
663
     * @param string|null $theme The name of the theme (See See self::AVAILABLE_THEMES for valid values). Set to null
664
     *                           if the system wide theme should be used.
665
     *
666
     * @return $this
667
     */
668
    public function setTheme(?string $theme): self
669
    {
670
        $this->theme = $theme;
671
672
        return $this;
673
    }
674
675
    /**
676
     * Gets the group to which this user belongs to.
677
     *
678
     * @return Group|null The group of this user. Null if this user does not have a group.
679
     */
680
    public function getGroup(): ?Group
681
    {
682
        return $this->group;
683
    }
684
685
    /**
686
     * Sets the group of this user.
687
     *
688
     * @param Group|null $group The new group of this user. Set to null if this user should not have a group.
689
     *
690
     * @return $this
691
     */
692
    public function setGroup(?Group $group): self
693
    {
694
        $this->group = $group;
695
696
        return $this;
697
    }
698
699
    /**
700
     * Return true if the user should do two-factor authentication.
701
     */
702
    public function isGoogleAuthenticatorEnabled(): bool
703
    {
704
        return (bool)$this->googleAuthenticatorSecret;
705
    }
706
707
    /**
708
     * Return the user name that should be shown in Google Authenticator.
709
     */
710
    public function getGoogleAuthenticatorUsername(): string
711
    {
712
        return $this->getUsername();
713
    }
714
715
    /**
716
     * Return the Google Authenticator secret
717
     * When an empty string is returned, the Google authentication is disabled.
718
     */
719
    public function getGoogleAuthenticatorSecret(): ?string
720
    {
721
        return $this->googleAuthenticatorSecret;
722
    }
723
724
    /**
725
     * Sets the secret used for Google Authenticator. Set to null to disable Google Authenticator.
726
     *
727
     * @return $this
728
     */
729
    public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): self
730
    {
731
        $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
732
733
        return $this;
734
    }
735
736
    /**
737
     * Check if the given code is a valid backup code.
738
     *
739
     * @param string $code the code that should be checked
740
     *
741
     * @return bool true if the backup code is valid
742
     */
743
    public function isBackupCode(string $code): bool
744
    {
745
        return in_array($code, $this->getBackupCodes(), true);
746
    }
747
748
    /**
749
     * Invalidate a backup code.
750
     *
751
     * @param string $code The code that should be invalidated
752
     */
753
    public function invalidateBackupCode(string $code): void
754
    {
755
        $key = array_search($code, $this->getBackupCodes(), true);
756
        if (false !== $key) {
757
            unset($this->backupCodes[$key]);
758
        }
759
    }
760
761
    /**
762
     * Returns the list of all valid backup codes.
763
     *
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
     *
774
     * @param string[] $codes An array containing the backup codes
775
     *
776
     * @return $this
777
     *
778
     * @throws Exception If an error with the datetime occurs
779
     */
780
    public function setBackupCodes(array $codes): self
781
    {
782
        $this->backupCodes = $codes;
783
        if (empty($codes)) {
784
            $this->backupCodesGenerationDate = null;
785
        } else {
786
            $this->backupCodesGenerationDate = new DateTime();
787
        }
788
789
        return $this;
790
    }
791
792
    /**
793
     * Return the date when the backup codes were generated.
794
     */
795
    public function getBackupCodesGenerationDate(): ?DateTime
796
    {
797
        return $this->backupCodesGenerationDate;
798
    }
799
800
    /**
801
     * Return version for the trusted device token. Increase version to invalidate all trusted token of the user.
802
     *
803
     * @return int The version of trusted device token
804
     */
805
    public function getTrustedTokenVersion(): int
806
    {
807
        return $this->trustedDeviceCookieVersion;
808
    }
809
810
    /**
811
     * Invalidate all trusted device tokens at once, by incrementing the token version.
812
     * You have to flush the changes to database afterwards.
813
     */
814
    public function invalidateTrustedDeviceTokens(): void
815
    {
816
        ++$this->trustedDeviceCookieVersion;
817
    }
818
819
    public function getPreferredTwoFactorProvider(): ?string
820
    {
821
        //If U2F is available then prefer it
822
        //if ($this->isU2FAuthEnabled()) {
823
        //    return 'u2f_two_factor';
824
        //}
825
826
        if ($this->isWebAuthnAuthenticatorEnabled()) {
827
            return 'webauthn_two_factor_provider';
828
        }
829
830
        //Otherwise use other methods
831
        return null;
832
    }
833
834
    public function isWebAuthnAuthenticatorEnabled(): bool
835
    {
836
        return count($this->u2fKeys) > 0
837
            || count($this->webauthn_keys) > 0;
838
    }
839
840
    public function getLegacyU2FKeys(): iterable
841
    {
842
        return $this->u2fKeys;
843
    }
844
845
    public function getWebAuthnUser(): PublicKeyCredentialUserEntity
846
    {
847
        return new PublicKeyCredentialUserEntity(
848
            $this->getUsername(),
849
            (string) $this->getId(),
850
            $this->getFullName(),
851
        );
852
    }
853
854
    public function getWebauthnKeys(): iterable
855
    {
856
        return $this->webauthn_keys;
857
    }
858
859
    public function addWebauthnKey(WebauthnKey $webauthnKey): void
860
    {
861
        $this->webauthn_keys->add($webauthnKey);
862
    }
863
}
864