Completed
Pull Request — develop (#320)
by Michiel
04:42
created

Identity::selfVetSecondFactor()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.3626
c 0
b 0
f 0
cc 7
nc 15
nop 4
1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\Stepup\Identity;
20
21
use Broadway\EventSourcing\EventSourcedAggregateRoot;
22
use Surfnet\Stepup\Configuration\InstitutionConfiguration;
23
use Surfnet\Stepup\Configuration\Value\Institution as ConfigurationInstitution;
24
use Surfnet\Stepup\DateTime\DateTime;
25
use Surfnet\Stepup\Exception\DomainException;
26
use Surfnet\Stepup\Helper\SecondFactorProvePossessionHelper;
27
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
28
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
29
use Surfnet\Stepup\Identity\Entity\RegistrationAuthorityCollection;
30
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
31
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
32
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
33
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
34
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
35
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
36
use Surfnet\Stepup\Identity\Event\AppointedAsRaaForInstitutionEvent;
37
use Surfnet\Stepup\Identity\Event\AppointedAsRaForInstitutionEvent;
38
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
39
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
40
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
41
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
42
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenAndVerifiedEvent;
43
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
44
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
45
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaForInstitutionEvent;
46
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
47
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaForInstitutionEvent;
48
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
49
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
50
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
51
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
52
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
53
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenAndVerifiedEvent;
54
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
55
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
56
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedForInstitutionEvent;
57
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
58
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedForInstitutionEvent;
59
use Surfnet\Stepup\Identity\Event\SecondFactorVettedWithoutTokenProofOfPossession;
60
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
61
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenAndVerifiedEvent;
62
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
63
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
64
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
65
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
66
use Surfnet\Stepup\Identity\Event\VettedSecondFactorsAllRevokedEvent;
67
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenAndVerifiedEvent;
68
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
69
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
70
use Surfnet\Stepup\Identity\Value\CommonName;
71
use Surfnet\Stepup\Identity\Value\ContactInformation;
72
use Surfnet\Stepup\Identity\Value\DocumentNumber;
73
use Surfnet\Stepup\Identity\Value\Email;
74
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow;
75
use Surfnet\Stepup\Identity\Value\GssfId;
76
use Surfnet\Stepup\Identity\Value\IdentityId;
77
use Surfnet\Stepup\Identity\Value\Institution;
78
use Surfnet\Stepup\Identity\Value\Locale;
79
use Surfnet\Stepup\Identity\Value\Location;
80
use Surfnet\Stepup\Identity\Value\NameId;
81
use Surfnet\Stepup\Identity\Value\PhoneNumber;
82
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole;
83
use Surfnet\Stepup\Identity\Value\SecondFactorId;
84
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
85
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifierFactory;
86
use Surfnet\Stepup\Identity\Value\StepupProvider;
87
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
88
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
89
use Surfnet\Stepup\Token\TokenGenerator;
90
use Surfnet\StepupBundle\Security\OtpGenerator;
91
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
92
use Surfnet\StepupBundle\Value\SecondFactorType;
93
use Surfnet\StepupMiddleware\ApiBundle\Identity\Service\SecondFactorService;
94
use function sprintf;
95
96
/**
97
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
98
 * @SuppressWarnings(PHPMD.TooManyMethods)
99
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
100
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
101
 * @SuppressWarnings(PHPMD.ExcessiveClassLength)
102
 */
103
class Identity extends EventSourcedAggregateRoot implements IdentityApi
104
{
105
    /**
106
     * @var IdentityId
107
     */
108
    private $id;
109
110
    /**
111
     * @var Institution
112
     */
113
    private $institution;
114
115
    /**
116
     * @var NameId
117
     */
118
    private $nameId;
119
120
    /**
121
     * @var \Surfnet\Stepup\Identity\Value\CommonName
122
     */
123
    private $commonName;
124
125
    /**
126
     * @var \Surfnet\Stepup\Identity\Value\Email
127
     */
128
    private $email;
129
130
    /**
131
     * @var SecondFactorCollection|UnverifiedSecondFactor[]
132
     */
133
    private $unverifiedSecondFactors;
134
135
    /**
136
     * @var SecondFactorCollection|VerifiedSecondFactor[]
137
     */
138
    private $verifiedSecondFactors;
139
140
    /**
141
     * @var SecondFactorCollection|VettedSecondFactor[]
142
     */
143
    private $vettedSecondFactors;
144
145
    /**
146
     * @var RegistrationAuthorityCollection
147
     */
148
    private $registrationAuthorities;
149
150
    /**
151
     * @var Locale
152
     */
153
    private $preferredLocale;
154
155
    /**
156
     * @var boolean
157
     */
158
    private $forgotten;
159
160
    /**
161
     * @var int
162
     */
163
    private $maxNumberOfTokens = 1;
164
165
    public static function create(
166
        IdentityId $id,
167
        Institution $institution,
168
        NameId $nameId,
169
        CommonName $commonName,
170
        Email $email,
171
        Locale $preferredLocale
172
    ) {
173
        $identity = new self();
174
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
175
176
        return $identity;
177
    }
178
179
    final public function __construct()
180
    {
181
    }
182
183
    public function rename(CommonName $commonName)
184
    {
185
        $this->assertNotForgotten();
186
187
        if ($this->commonName->equals($commonName)) {
188
            return;
189
        }
190
191
        $this->commonName = $commonName;
192
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
193
    }
194
195
    public function changeEmail(Email $email)
196
    {
197
        $this->assertNotForgotten();
198
199
        if ($this->email->equals($email)) {
200
            return;
201
        }
202
203
        $this->email = $email;
204
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
205
    }
206
207
    /**
208
     * @param int $numberOfTokens
209
     */
210
    public function setMaxNumberOfTokens($numberOfTokens)
211
    {
212
        $this->maxNumberOfTokens = $numberOfTokens;
213
    }
214
215
    public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId)
216
    {
217
        $this->assertNotForgotten();
218
        $this->assertUserMayAddSecondFactor();
219
220
        $this->apply(
221
            new YubikeySecondFactorBootstrappedEvent(
222
                $this->id,
223
                $this->nameId,
224
                $this->institution,
225
                $this->commonName,
226
                $this->email,
227
                $this->preferredLocale,
228
                $secondFactorId,
229
                $yubikeyPublicId
230
            )
231
        );
232
    }
233
234
    public function provePossessionOfYubikey(
235
        SecondFactorId $secondFactorId,
236
        YubikeyPublicId $yubikeyPublicId,
237
        $emailVerificationRequired,
238
        EmailVerificationWindow $emailVerificationWindow
239
    ) {
240
        $this->assertNotForgotten();
241
        $this->assertUserMayAddSecondFactor();
242
243
        if ($emailVerificationRequired) {
244
            $emailVerificationNonce = TokenGenerator::generateNonce();
245
246
            $this->apply(
247
                new YubikeyPossessionProvenEvent(
248
                    $this->id,
249
                    $this->institution,
250
                    $secondFactorId,
251
                    $yubikeyPublicId,
252
                    $emailVerificationRequired,
253
                    $emailVerificationWindow,
254
                    $emailVerificationNonce,
255
                    $this->commonName,
256
                    $this->email,
257
                    $this->preferredLocale
258
                )
259
            );
260
        } else {
261
            $this->apply(
262
                new YubikeyPossessionProvenAndVerifiedEvent(
263
                    $this->id,
264
                    $this->institution,
265
                    $secondFactorId,
266
                    $yubikeyPublicId,
267
                    $this->commonName,
268
                    $this->email,
269
                    $this->preferredLocale,
270
                    DateTime::now(),
271
                    OtpGenerator::generate(8)
272
                )
273
            );
274
        }
275
    }
276
277
    public function provePossessionOfPhone(
278
        SecondFactorId $secondFactorId,
279
        PhoneNumber $phoneNumber,
280
        $emailVerificationRequired,
281
        EmailVerificationWindow $emailVerificationWindow
282
    ) {
283
        $this->assertNotForgotten();
284
        $this->assertUserMayAddSecondFactor();
285
286
        if ($emailVerificationRequired) {
287
            $emailVerificationNonce = TokenGenerator::generateNonce();
288
289
            $this->apply(
290
                new PhonePossessionProvenEvent(
291
                    $this->id,
292
                    $this->institution,
293
                    $secondFactorId,
294
                    $phoneNumber,
295
                    $emailVerificationRequired,
296
                    $emailVerificationWindow,
297
                    $emailVerificationNonce,
298
                    $this->commonName,
299
                    $this->email,
300
                    $this->preferredLocale
301
                )
302
            );
303
        } else {
304
            $this->apply(
305
                new PhonePossessionProvenAndVerifiedEvent(
306
                    $this->id,
307
                    $this->institution,
308
                    $secondFactorId,
309
                    $phoneNumber,
310
                    $this->commonName,
311
                    $this->email,
312
                    $this->preferredLocale,
313
                    DateTime::now(),
314
                    OtpGenerator::generate(8)
315
                )
316
            );
317
        }
318
    }
319
320
    public function provePossessionOfGssf(
321
        SecondFactorId $secondFactorId,
322
        StepupProvider $provider,
323
        GssfId $gssfId,
324
        $emailVerificationRequired,
325
        EmailVerificationWindow $emailVerificationWindow
326
    ) {
327
        $this->assertNotForgotten();
328
        $this->assertUserMayAddSecondFactor();
329
330
        if ($emailVerificationRequired) {
331
            $emailVerificationNonce = TokenGenerator::generateNonce();
332
333
            $this->apply(
334
                new GssfPossessionProvenEvent(
335
                    $this->id,
336
                    $this->institution,
337
                    $secondFactorId,
338
                    $provider,
339
                    $gssfId,
340
                    $emailVerificationRequired,
341
                    $emailVerificationWindow,
342
                    $emailVerificationNonce,
343
                    $this->commonName,
344
                    $this->email,
345
                    $this->preferredLocale
346
                )
347
            );
348
        } else {
349
            $this->apply(
350
                new GssfPossessionProvenAndVerifiedEvent(
351
                    $this->id,
352
                    $this->institution,
353
                    $secondFactorId,
354
                    $provider,
355
                    $gssfId,
356
                    $this->commonName,
357
                    $this->email,
358
                    $this->preferredLocale,
359
                    DateTime::now(),
360
                    OtpGenerator::generate(8)
361
                )
362
            );
363
        }
364
    }
365
366
    public function provePossessionOfU2fDevice(
367
        SecondFactorId $secondFactorId,
368
        U2fKeyHandle $keyHandle,
369
        $emailVerificationRequired,
370
        EmailVerificationWindow $emailVerificationWindow
371
    ) {
372
        $this->assertNotForgotten();
373
        $this->assertUserMayAddSecondFactor();
374
375
        if ($emailVerificationRequired) {
376
            $emailVerificationNonce = TokenGenerator::generateNonce();
377
378
            $this->apply(
379
                new U2fDevicePossessionProvenEvent(
380
                    $this->id,
381
                    $this->institution,
382
                    $secondFactorId,
383
                    $keyHandle,
384
                    $emailVerificationRequired,
385
                    $emailVerificationWindow,
386
                    $emailVerificationNonce,
387
                    $this->commonName,
388
                    $this->email,
389
                    $this->preferredLocale
390
                )
391
            );
392
        } else {
393
            $this->apply(
394
                new U2fDevicePossessionProvenAndVerifiedEvent(
395
                    $this->id,
396
                    $this->institution,
397
                    $secondFactorId,
398
                    $keyHandle,
399
                    $this->commonName,
400
                    $this->email,
401
                    $this->preferredLocale,
402
                    DateTime::now(),
403
                    OtpGenerator::generate(8)
404
                )
405
            );
406
        }
407
    }
408
409
    public function verifyEmail($verificationNonce)
410
    {
411
        $this->assertNotForgotten();
412
413
        $secondFactorToVerify = null;
414
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
415
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
416
            if ($secondFactor->hasNonce($verificationNonce)) {
417
                $secondFactorToVerify = $secondFactor;
418
            }
419
        }
420
421
        if (!$secondFactorToVerify) {
422
            throw new DomainException(
423
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
424
            );
425
        }
426
427
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
428
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
429
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
430
        }
431
432
        $secondFactorToVerify->verifyEmail();
433
    }
434
435
    /**
436
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
437
     */
438
    public function vetSecondFactor(
439
        IdentityApi $registrant,
440
        SecondFactorId $registrantsSecondFactorId,
441
        SecondFactorType $registrantsSecondFactorType,
442
        SecondFactorIdentifier $registrantsSecondFactorIdentifier,
443
        $registrationCode,
444
        DocumentNumber $documentNumber,
445
        $identityVerified,
446
        SecondFactorTypeService $secondFactorTypeService,
447
        SecondFactorProvePossessionHelper $secondFactorProvePossessionHelper,
448
        $provePossessionSkipped
449
    ) {
450
        $this->assertNotForgotten();
451
452
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
453
        $secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa($secondFactorTypeService);
454
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
455
456
        if ($registrantsSecondFactor === null) {
457
            throw new DomainException(
458
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId)
459
            );
460
        }
461
462
        if ($secondFactorWithHighestLoa === null) {
463
            throw new DomainException(
464
                sprintf(
465
                    'Vetting failed: authority %s has %d vetted second factors!',
466
                    $this->id,
467
                    count($this->vettedSecondFactors)
468
                )
469
            );
470
        }
471
472
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo(
473
            $registrantsSecondFactor,
474
            $secondFactorTypeService
475
        )) {
476
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
477
        }
478
479
        if (!$identityVerified) {
480
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
481
        }
482
483
        if ($provePossessionSkipped && !$secondFactorProvePossessionHelper->canSkipProvePossession($registrantsSecondFactorType)) {
484
            throw new DomainException(sprintf(
485
                "The possession of registrants second factor with ID '%s' of type '%s' has to be physically proven",
486
                $registrantsSecondFactorId,
487
                $registrantsSecondFactorType->getSecondFactorType()
488
            ));
489
        }
490
491
        $registrant->complyWithVettingOfSecondFactor(
492
            $registrantsSecondFactorId,
493
            $registrantsSecondFactorType,
494
            $registrantsSecondFactorIdentifier,
495
            $registrationCode,
496
            $documentNumber,
497
            $provePossessionSkipped
498
        );
499
    }
500
501
    /**
502
     * Self vetting, is when the user uses it's own token to vet another.
503
     *
504
     * Here the new token should have a lower or equal LoA to that of the one in posession of the user
505
     */
506
    public function selfVetSecondFactor(
507
        SecondFactorId $authoringSecondFactorId,
508
        string $registrationCode,
509
        SecondFactorIdentifier $secondFactorIdentifier,
510
        SecondFactorTypeService $secondFactorTypeService
511
    ): void {
512
        $this->assertNotForgotten();
513
        $registeringSecondFactor = null;
514
        foreach ($this->verifiedSecondFactors as $secondFactor) {
515
            /** @var VerifiedSecondFactor $secondFactor */
516
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
517
                $registeringSecondFactor = $secondFactor;
518
            }
519
        }
520
521
        if ($registeringSecondFactor === null) {
522
            throw new DomainException(
523
                sprintf(
524
                    'Registrant second factor of type %s with ID %s does not exist',
525
                    get_class($secondFactorIdentifier),
526
                    $secondFactorIdentifier->getValue()
527
                )
528
            );
529
        }
530
531
        if (!$registeringSecondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
532
            throw new DomainException('The verified second factors registration code or identifier do not match.');
533
        }
534
        $authoringSecondFactor = $this->getVettedSecondFactor($authoringSecondFactorId);
535
        if ($authoringSecondFactor === null) {
536
            throw new DomainException(
537
                sprintf('Authorizing second factor with ID %s does not exist', $authoringSecondFactorId)
538
            );
539
        }
540
        if ($authoringSecondFactor->hasEqualOrHigherLoaComparedTo($registeringSecondFactor, $secondFactorTypeService)) {
541
            throw new DomainException("The second factor to be vetted has a higher LoA then the Token used for proving posession");
542
        }
543
        $registeringSecondFactor->vet(DocumentNumber::unknown(), true);
544
    }
545
546
    public function complyWithVettingOfSecondFactor(
547
        SecondFactorId $secondFactorId,
548
        SecondFactorType $secondFactorType,
549
        SecondFactorIdentifier $secondFactorIdentifier,
550
        $registrationCode,
551
        DocumentNumber $documentNumber,
552
        $provePossessionSkipped
553
    ) {
554
        $this->assertNotForgotten();
555
556
        $secondFactorToVet = null;
557
        foreach ($this->verifiedSecondFactors as $secondFactor) {
558
            /** @var VerifiedSecondFactor $secondFactor */
559
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
560
                $secondFactorToVet = $secondFactor;
561
            }
562
        }
563
564
        if (!$secondFactorToVet) {
565
            throw new DomainException(
566
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
567
                'and second factor identifier'
568
            );
569
        }
570
571
        if (!$secondFactorToVet->canBeVettedNow()) {
572
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
573
        }
574
575
        $secondFactorToVet->vet($documentNumber, $provePossessionSkipped);
576
    }
577
578
    public function revokeSecondFactor(SecondFactorId $secondFactorId)
579
    {
580
        $this->assertNotForgotten();
581
582
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
583
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
584
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
585
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
586
        /** @var VettedSecondFactor|null $vettedSecondFactor */
587
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
588
589
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
590
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
591
        }
592
593
        if ($unverifiedSecondFactor) {
594
            $unverifiedSecondFactor->revoke();
595
596
            return;
597
        }
598
599
        if ($verifiedSecondFactor) {
600
            $verifiedSecondFactor->revoke();
601
602
            return;
603
        }
604
605
        $vettedSecondFactor->revoke();
606
607
        if ($this->vettedSecondFactors->isEmpty()) {
608
            $this->allVettedSecondFactorsRemoved();
609
        }
610
    }
611
612
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId)
613
    {
614
        $this->assertNotForgotten();
615
616
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
617
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
618
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
619
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
620
        /** @var VettedSecondFactor|null $vettedSecondFactor */
621
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
622
623
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
624
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
625
        }
626
627
        if ($unverifiedSecondFactor) {
628
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
629
630
            return;
631
        }
632
633
        if ($verifiedSecondFactor) {
634
            $verifiedSecondFactor->complyWithRevocation($authorityId);
635
636
            return;
637
        }
638
639
        $vettedSecondFactor->complyWithRevocation($authorityId);
640
641
        if ($this->vettedSecondFactors->isEmpty()) {
642
            $this->allVettedSecondFactorsRemoved();
643
        }
644
    }
645
646
    /**
647
     * @param RegistrationAuthorityRole $role
648
     * @param Institution $institution
649
     * @param Location $location
650
     * @param ContactInformation $contactInformation
651
     * @param InstitutionConfiguration $institutionConfiguration
652
     * @return void
653
     */
654
    public function accreditWith(
655
        RegistrationAuthorityRole $role,
656
        Institution $institution,
657
        Location $location,
658
        ContactInformation $contactInformation,
659
        InstitutionConfiguration $institutionConfiguration
660
    ) {
661
        $this->assertNotForgotten();
662
663
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(new ConfigurationInstitution($this->institution->getInstitution()))) {
664
            throw new DomainException('An Identity may only be accredited by configured institutions.');
665
        }
666
667
        if (!$this->vettedSecondFactors->count()) {
668
            throw new DomainException(
669
                'An Identity must have at least one vetted second factor before it can be accredited'
670
            );
671
        }
672
673
        if ($this->registrationAuthorities->exists($institution)) {
674
            throw new DomainException('Cannot accredit Identity as it has already been accredited for institution');
675
        }
676
677
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
678
            $this->apply(new IdentityAccreditedAsRaForInstitutionEvent(
679
                $this->id,
680
                $this->nameId,
681
                $this->institution,
682
                $role,
683
                $location,
684
                $contactInformation,
685
                $institution
686
            ));
687
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
688
            $this->apply(new IdentityAccreditedAsRaaForInstitutionEvent(
689
                $this->id,
690
                $this->nameId,
691
                $this->institution,
692
                $role,
693
                $location,
694
                $contactInformation,
695
                $institution
696
            ));
697
        } else {
698
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
699
        }
700
    }
701
702
    public function amendRegistrationAuthorityInformation(Institution $institution, Location $location, ContactInformation $contactInformation)
703
    {
704
        $this->assertNotForgotten();
705
706
        if (!$this->registrationAuthorities->exists($institution)) {
707
            throw new DomainException(
708
                'Cannot amend registration authority information: identity is not a registration authority for institution'
709
            );
710
        }
711
712
        $this->apply(
713
            new RegistrationAuthorityInformationAmendedForInstitutionEvent(
714
                $this->id,
715
                $this->institution,
716
                $this->nameId,
717
                $location,
718
                $contactInformation,
719
                $institution
720
            )
721
        );
722
    }
723
724
    /**
725
     * This method will appoint an institution to become ra or raa for another institution
726
     *
727
     * @param Institution $institution
728
     * @param RegistrationAuthorityRole $role
729
     * @param InstitutionConfiguration $institutionConfiguration
730
     */
731
    public function appointAs(
732
        Institution $institution,
733
        RegistrationAuthorityRole $role,
734
        InstitutionConfiguration $institutionConfiguration
735
    ) {
736
        $this->assertNotForgotten();
737
738
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(new ConfigurationInstitution($this->institution->getInstitution()))) {
739
            throw new DomainException(
740
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority for institution'
741
            );
742
        }
743
744
        $registrationAuthority = $this->registrationAuthorities->get($institution);
745
746
        if ($registrationAuthority->isAppointedAs($role)) {
747
            return;
748
        }
749
750
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
751
            $this->apply(new AppointedAsRaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution));
752
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
753
            $this->apply(new AppointedAsRaaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution));
754
        } else {
755
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
756
        }
757
    }
758
759
    public function retractRegistrationAuthority(Institution $institution)
760
    {
761
        $this->assertNotForgotten();
762
763
        if (!$this->registrationAuthorities->exists($institution)) {
764
            throw new DomainException(
765
                'Cannot Retract Registration Authority as the Identity is not a registration authority'
766
            );
767
        }
768
769
        $this->apply(new RegistrationAuthorityRetractedForInstitutionEvent(
770
            $this->id,
771
            $this->institution,
772
            $this->nameId,
773
            $this->commonName,
774
            $this->email,
775
            $institution
776
        ));
777
    }
778
779
    public function expressPreferredLocale(Locale $preferredLocale)
780
    {
781
        $this->assertNotForgotten();
782
783
        if ($this->preferredLocale === $preferredLocale) {
784
            return;
785
        }
786
787
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
788
    }
789
790
    public function forget()
791
    {
792
        $this->assertNotForgotten();
793
794
        if ($this->registrationAuthorities->count()) {
795
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
796
        }
797
798
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
799
    }
800
801
    public function allVettedSecondFactorsRemoved()
802
    {
803
        $this->apply(
804
            new VettedSecondFactorsAllRevokedEvent(
805
                $this->id,
806
                $this->institution
807
            )
808
        );
809
    }
810
811
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event)
812
    {
813
        $this->id = $event->identityId;
814
        $this->institution = $event->identityInstitution;
815
        $this->nameId = $event->nameId;
816
        $this->commonName = $event->commonName;
817
        $this->email = $event->email;
818
        $this->preferredLocale = $event->preferredLocale;
819
        $this->forgotten = false;
820
821
        $this->unverifiedSecondFactors = new SecondFactorCollection();
822
        $this->verifiedSecondFactors = new SecondFactorCollection();
823
        $this->vettedSecondFactors = new SecondFactorCollection();
824
        $this->registrationAuthorities = new RegistrationAuthorityCollection();
825
    }
826
827
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event)
828
    {
829
        $this->commonName = $event->commonName;
830
    }
831
832
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event)
833
    {
834
        $this->email = $event->email;
835
    }
836
837
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event)
838
    {
839
        $secondFactor = VettedSecondFactor::create(
840
            $event->secondFactorId,
841
            $this,
842
            new SecondFactorType('yubikey'),
843
            $event->yubikeyPublicId
844
        );
845
846
        $this->vettedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
847
    }
848
849
    protected function applyYubikeyPossessionProvenEvent(YubikeyPossessionProvenEvent $event)
850
    {
851
        $secondFactor = UnverifiedSecondFactor::create(
852
            $event->secondFactorId,
853
            $this,
854
            new SecondFactorType('yubikey'),
855
            $event->yubikeyPublicId,
856
            $event->emailVerificationWindow,
857
            $event->emailVerificationNonce
858
        );
859
860
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
861
    }
862
863
    protected function applyYubikeyPossessionProvenAndVerifiedEvent(YubikeyPossessionProvenAndVerifiedEvent $event)
864
    {
865
        $secondFactor = VerifiedSecondFactor::create(
866
            $event->secondFactorId,
867
            $this,
868
            new SecondFactorType('yubikey'),
869
            $event->yubikeyPublicId,
870
            $event->registrationRequestedAt,
871
            $event->registrationCode
872
        );
873
874
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
875
    }
876
877
    protected function applyPhonePossessionProvenEvent(PhonePossessionProvenEvent $event)
878
    {
879
        $secondFactor = UnverifiedSecondFactor::create(
880
            $event->secondFactorId,
881
            $this,
882
            new SecondFactorType('sms'),
883
            $event->phoneNumber,
884
            $event->emailVerificationWindow,
885
            $event->emailVerificationNonce
886
        );
887
888
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
889
    }
890
891
    protected function applyPhonePossessionProvenAndVerifiedEvent(PhonePossessionProvenAndVerifiedEvent $event)
892
    {
893
        $secondFactor = VerifiedSecondFactor::create(
894
            $event->secondFactorId,
895
            $this,
896
            new SecondFactorType('sms'),
897
            $event->phoneNumber,
898
            $event->registrationRequestedAt,
899
            $event->registrationCode
900
        );
901
902
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
903
    }
904
905
    protected function applyGssfPossessionProvenEvent(GssfPossessionProvenEvent $event)
906
    {
907
        $secondFactor = UnverifiedSecondFactor::create(
908
            $event->secondFactorId,
909
            $this,
910
            new SecondFactorType((string)$event->stepupProvider),
911
            $event->gssfId,
912
            $event->emailVerificationWindow,
913
            $event->emailVerificationNonce
914
        );
915
916
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
917
    }
918
919
    protected function applyGssfPossessionProvenAndVerifiedEvent(GssfPossessionProvenAndVerifiedEvent $event)
920
    {
921
        $secondFactor = VerifiedSecondFactor::create(
922
            $event->secondFactorId,
923
            $this,
924
            new SecondFactorType((string)$event->stepupProvider),
925
            $event->gssfId,
926
            $event->registrationRequestedAt,
927
            $event->registrationCode
928
        );
929
930
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
931
    }
932
933
    protected function applyU2fDevicePossessionProvenEvent(U2fDevicePossessionProvenEvent $event)
934
    {
935
        $secondFactor = UnverifiedSecondFactor::create(
936
            $event->secondFactorId,
937
            $this,
938
            new SecondFactorType('u2f'),
939
            $event->keyHandle,
940
            $event->emailVerificationWindow,
941
            $event->emailVerificationNonce
942
        );
943
944
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
945
    }
946
947
    protected function applyU2fDevicePossessionProvenAndVerifiedEvent(U2fDevicePossessionProvenAndVerifiedEvent $event)
948
    {
949
        $secondFactor = VerifiedSecondFactor::create(
950
            $event->secondFactorId,
951
            $this,
952
            new SecondFactorType('u2f'),
953
            $event->keyHandle,
954
            $event->registrationRequestedAt,
955
            $event->registrationCode
956
        );
957
958
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
959
    }
960
961
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event)
962
    {
963
        $secondFactorId = (string)$event->secondFactorId;
964
965
        /** @var UnverifiedSecondFactor $unverified */
966
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
967
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
968
969
        $this->unverifiedSecondFactors->remove($secondFactorId);
970
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
971
    }
972
973
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event)
974
    {
975
        $secondFactorId = (string)$event->secondFactorId;
976
977
        /** @var VerifiedSecondFactor $verified */
978
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
979
        $vetted = $verified->asVetted();
980
981
        $this->verifiedSecondFactors->remove($secondFactorId);
982
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
983
    }
984
985
    protected function applySecondFactorVettedWithoutTokenProofOfPossession(SecondFactorVettedWithoutTokenProofOfPossession $event)
986
    {
987
        $secondFactorId = (string)$event->secondFactorId;
988
989
        /** @var VerifiedSecondFactor $verified */
990
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
991
        $vetted = $verified->asVetted();
992
993
        $this->verifiedSecondFactors->remove($secondFactorId);
994
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
995
    }
996
997
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event)
998
    {
999
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
1000
    }
1001
1002
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
1003
        CompliedWithUnverifiedSecondFactorRevocationEvent $event
1004
    ) {
1005
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
1006
    }
1007
1008
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event)
1009
    {
1010
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
1011
    }
1012
1013
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
1014
        CompliedWithVerifiedSecondFactorRevocationEvent $event
1015
    ) {
1016
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
1017
    }
1018
1019
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event)
1020
    {
1021
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
1022
    }
1023
1024
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
1025
        CompliedWithVettedSecondFactorRevocationEvent $event
1026
    ) {
1027
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
1028
    }
1029
1030
    protected function applyIdentityAccreditedAsRaForInstitutionEvent(IdentityAccreditedAsRaForInstitutionEvent $event)
1031
    {
1032
        $this->registrationAuthorities->set($event->raInstitution, RegistrationAuthority::accreditWith(
1033
            $event->registrationAuthorityRole,
1034
            $event->location,
1035
            $event->contactInformation,
1036
            $event->raInstitution
1037
        ));
1038
    }
1039
1040
    protected function applyIdentityAccreditedAsRaaForInstitutionEvent(IdentityAccreditedAsRaaForInstitutionEvent $event)
1041
    {
1042
        $this->registrationAuthorities->set($event->raInstitution, RegistrationAuthority::accreditWith(
1043
            $event->registrationAuthorityRole,
1044
            $event->location,
1045
            $event->contactInformation,
1046
            $event->raInstitution
1047
        ));
1048
    }
1049
1050
    protected function applyRegistrationAuthorityInformationAmendedForInstitutionEvent(
1051
        RegistrationAuthorityInformationAmendedForInstitutionEvent $event
1052
    ) {
1053
        $this->registrationAuthorities->get($event->raInstitution)->amendInformation($event->location, $event->contactInformation);
1054
    }
1055
1056
    protected function applyAppointedAsRaaForInstitutionEvent(AppointedAsRaaForInstitutionEvent $event)
1057
    {
1058
        $this->registrationAuthorities->get($event->raInstitution)->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1059
    }
1060
1061
    protected function applyRegistrationAuthorityRetractedForInstitutionEvent(RegistrationAuthorityRetractedForInstitutionEvent $event)
1062
    {
1063
        $this->registrationAuthorities->remove($event->raInstitution);
1064
    }
1065
1066
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event)
1067
    {
1068
        $this->preferredLocale = $event->preferredLocale;
1069
    }
1070
1071
    protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

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

Loading history...
1072
    {
1073
        $this->commonName = CommonName::unknown();
1074
        $this->email = Email::unknown();
1075
        $this->forgotten = true;
1076
    }
1077
1078
    /**
1079
     * This method is kept to be backwards compatible for changes before FGA
1080
     *
1081
     * @param AppointedAsRaEvent $event
1082
     */
1083
    protected function applyAppointedAsRaEvent(AppointedAsRaEvent $event)
1084
    {
1085
        $this->registrationAuthorities->get($event->identityInstitution)
1086
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1087
    }
1088
1089
    /**
1090
     * This method is kept to be backwards compatible for changes before FGA
1091
     *
1092
     * @param AppointedAsRaaEvent $event
1093
     */
1094
    protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $event)
1095
    {
1096
        $this->registrationAuthorities->get($event->identityInstitution)
1097
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1098
    }
1099
1100
    /**
1101
     * This method is kept to be backwards compatible for changes before FGA
1102
     *
1103
     * @param AppointedAsRaaEvent $event
0 ignored issues
show
Documentation introduced by
Should the type for parameter $event not be IdentityAccreditedAsRaEvent?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1104
     */
1105
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event)
1106
    {
1107
        $this->registrationAuthorities->set($event->identityInstitution, RegistrationAuthority::accreditWith(
1108
            $event->registrationAuthorityRole,
1109
            $event->location,
1110
            $event->contactInformation,
1111
            $event->identityInstitution
1112
        ));
1113
    }
1114
1115
    /**
1116
     * This method is kept to be backwards compatible for changes before FGA
1117
     *
1118
     * @param IdentityAccreditedAsRaaEvent $event
1119
     */
1120
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event)
1121
    {
1122
        $this->registrationAuthorities->set($event->identityInstitution, RegistrationAuthority::accreditWith(
1123
            $event->registrationAuthorityRole,
1124
            $event->location,
1125
            $event->contactInformation,
1126
            $event->identityInstitution
1127
        ));
1128
    }
1129
1130
    /**
1131
     * This method is kept to be backwards compatible for changes before FGA
1132
     *
1133
     * @param AppointedAsRaForInstitutionEvent $event
1134
     */
1135
    protected function applyAppointedAsRaForInstitutionEvent(AppointedAsRaForInstitutionEvent $event)
1136
    {
1137
        $this->registrationAuthorities->get($event->identityInstitution)
1138
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1139
    }
1140
1141
    /**
1142
     * This method is kept to be backwards compatible for changes before FGA
1143
     *
1144
     * @param RegistrationAuthorityInformationAmendedEvent $event
1145
     */
1146
    protected function applyRegistrationAuthorityInformationAmendedEvent(
1147
        RegistrationAuthorityInformationAmendedEvent $event
1148
    ) {
1149
        $this->registrationAuthorities->get($event->identityInstitution)->amendInformation($event->location, $event->contactInformation);
1150
    }
1151
1152
    /**
1153
     * This method is kept to be backwards compatible for changes before FGA
1154
     *
1155
     * @param RegistrationAuthorityRetractedEvent $event
1156
     */
1157
    protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $event)
1158
    {
1159
        $this->registrationAuthorities->remove($event->identityInstitution);
1160
    }
1161
1162
1163
    public function getAggregateRootId(): string
1164
    {
1165
        return $this->id->getIdentityId();
1166
    }
1167
1168
    protected function getChildEntities(): array
1169
    {
1170
        return array_merge(
1171
            $this->unverifiedSecondFactors->getValues(),
1172
            $this->verifiedSecondFactors->getValues(),
1173
            $this->vettedSecondFactors->getValues(),
1174
            $this->registrationAuthorities->getValues()
1175
        );
1176
    }
1177
1178
    /**
1179
     * @throws DomainException
1180
     */
1181
    private function assertNotForgotten()
1182
    {
1183
        if ($this->forgotten) {
1184
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
1185
        }
1186
    }
1187
1188
    /**
1189
     * @throws DomainException
1190
     */
1191
    private function assertUserMayAddSecondFactor()
1192
    {
1193
        if (count($this->unverifiedSecondFactors) +
1194
            count($this->verifiedSecondFactors) +
1195
            count($this->vettedSecondFactors) >= $this->maxNumberOfTokens
1196
        ) {
1197
            throw new DomainException(
1198
                sprintf('User may not have more than %d token(s)', $this->maxNumberOfTokens)
1199
            );
1200
        }
1201
    }
1202
1203
    public function getId()
1204
    {
1205
        return $this->id;
1206
    }
1207
1208
    /**
1209
     * @return NameId
1210
     */
1211
    public function getNameId()
1212
    {
1213
        return $this->nameId;
1214
    }
1215
1216
    /**
1217
     * @return Institution
1218
     */
1219
    public function getInstitution()
1220
    {
1221
        return $this->institution;
1222
    }
1223
1224
    public function getCommonName()
1225
    {
1226
        return $this->commonName;
1227
    }
1228
1229
    public function getEmail()
1230
    {
1231
        return $this->email;
1232
    }
1233
1234
    public function getPreferredLocale()
1235
    {
1236
        return $this->preferredLocale;
1237
    }
1238
1239
    /**
1240
     * @param SecondFactorId $secondFactorId
1241
     * @return VerifiedSecondFactor|null
1242
     */
1243
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId)
1244
    {
1245
        return $this->verifiedSecondFactors->get((string)$secondFactorId);
1246
    }
1247
1248
    private function getVettedSecondFactor(SecondFactorId $secondFactorId): ?VettedSecondFactor
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1249
    {
1250
        return $this->vettedSecondFactors->get((string)$secondFactorId);
1251
    }
1252
}
1253