Completed
Pull Request — develop (#320)
by Michiel
05:27 queued 02:41
created

Identity::selfVetSecondFactor()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.4266
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('Registrant second factor of type %s with ID %s does not exist', get_class($secondFactorIdentifier), $secondFactorIdentifier->getValue())
524
            );
525
        }
526
527
        if (!$registeringSecondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
528
            throw new DomainException('The verified second factors registration code or identifier do not match.');
529
        }
530
        $authoringSecondFactor = $this->getVettedSecondFactor($authoringSecondFactorId);
531
        if ($authoringSecondFactor === null) {
532
            throw new DomainException(
533
                sprintf('Authorizing second factor with ID %s does not exist', $authoringSecondFactorId)
534
            );
535
        }
536
        if ($authoringSecondFactor->hasEqualOrHigherLoaComparedTo($registeringSecondFactor, $secondFactorTypeService)) {
537
            throw new DomainException("The second factor to be vetted has a higher LoA then the Token used for proving posession");
538
        }
539
        $registeringSecondFactor->vet(DocumentNumber::unknown(), true);
540
    }
541
542
    public function complyWithVettingOfSecondFactor(
543
        SecondFactorId $secondFactorId,
544
        SecondFactorType $secondFactorType,
545
        SecondFactorIdentifier $secondFactorIdentifier,
546
        $registrationCode,
547
        DocumentNumber $documentNumber,
548
        $provePossessionSkipped
549
    ) {
550
        $this->assertNotForgotten();
551
552
        $secondFactorToVet = null;
553
        foreach ($this->verifiedSecondFactors as $secondFactor) {
554
            /** @var VerifiedSecondFactor $secondFactor */
555
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
556
                $secondFactorToVet = $secondFactor;
557
            }
558
        }
559
560
        if (!$secondFactorToVet) {
561
            throw new DomainException(
562
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
563
                'and second factor identifier'
564
            );
565
        }
566
567
        if (!$secondFactorToVet->canBeVettedNow()) {
568
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
569
        }
570
571
        $secondFactorToVet->vet($documentNumber, $provePossessionSkipped);
572
    }
573
574
    public function revokeSecondFactor(SecondFactorId $secondFactorId)
575
    {
576
        $this->assertNotForgotten();
577
578
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
579
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
580
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
581
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
582
        /** @var VettedSecondFactor|null $vettedSecondFactor */
583
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
584
585
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
586
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
587
        }
588
589
        if ($unverifiedSecondFactor) {
590
            $unverifiedSecondFactor->revoke();
591
592
            return;
593
        }
594
595
        if ($verifiedSecondFactor) {
596
            $verifiedSecondFactor->revoke();
597
598
            return;
599
        }
600
601
        $vettedSecondFactor->revoke();
602
603
        if ($this->vettedSecondFactors->isEmpty()) {
604
            $this->allVettedSecondFactorsRemoved();
605
        }
606
    }
607
608
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId)
609
    {
610
        $this->assertNotForgotten();
611
612
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
613
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
614
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
615
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
616
        /** @var VettedSecondFactor|null $vettedSecondFactor */
617
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
618
619
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
620
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
621
        }
622
623
        if ($unverifiedSecondFactor) {
624
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
625
626
            return;
627
        }
628
629
        if ($verifiedSecondFactor) {
630
            $verifiedSecondFactor->complyWithRevocation($authorityId);
631
632
            return;
633
        }
634
635
        $vettedSecondFactor->complyWithRevocation($authorityId);
636
637
        if ($this->vettedSecondFactors->isEmpty()) {
638
            $this->allVettedSecondFactorsRemoved();
639
        }
640
    }
641
642
    /**
643
     * @param RegistrationAuthorityRole $role
644
     * @param Institution $institution
645
     * @param Location $location
646
     * @param ContactInformation $contactInformation
647
     * @param InstitutionConfiguration $institutionConfiguration
648
     * @return void
649
     */
650
    public function accreditWith(
651
        RegistrationAuthorityRole $role,
652
        Institution $institution,
653
        Location $location,
654
        ContactInformation $contactInformation,
655
        InstitutionConfiguration $institutionConfiguration
656
    ) {
657
        $this->assertNotForgotten();
658
659
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(new ConfigurationInstitution($this->institution->getInstitution()))) {
660
            throw new DomainException('An Identity may only be accredited by configured institutions.');
661
        }
662
663
        if (!$this->vettedSecondFactors->count()) {
664
            throw new DomainException(
665
                'An Identity must have at least one vetted second factor before it can be accredited'
666
            );
667
        }
668
669
        if ($this->registrationAuthorities->exists($institution)) {
670
            throw new DomainException('Cannot accredit Identity as it has already been accredited for institution');
671
        }
672
673
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
674
            $this->apply(new IdentityAccreditedAsRaForInstitutionEvent(
675
                $this->id,
676
                $this->nameId,
677
                $this->institution,
678
                $role,
679
                $location,
680
                $contactInformation,
681
                $institution
682
            ));
683
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
684
            $this->apply(new IdentityAccreditedAsRaaForInstitutionEvent(
685
                $this->id,
686
                $this->nameId,
687
                $this->institution,
688
                $role,
689
                $location,
690
                $contactInformation,
691
                $institution
692
            ));
693
        } else {
694
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
695
        }
696
    }
697
698
    public function amendRegistrationAuthorityInformation(Institution $institution, Location $location, ContactInformation $contactInformation)
699
    {
700
        $this->assertNotForgotten();
701
702
        if (!$this->registrationAuthorities->exists($institution)) {
703
            throw new DomainException(
704
                'Cannot amend registration authority information: identity is not a registration authority for institution'
705
            );
706
        }
707
708
        $this->apply(
709
            new RegistrationAuthorityInformationAmendedForInstitutionEvent(
710
                $this->id,
711
                $this->institution,
712
                $this->nameId,
713
                $location,
714
                $contactInformation,
715
                $institution
716
            )
717
        );
718
    }
719
720
    /**
721
     * This method will appoint an institution to become ra or raa for another institution
722
     *
723
     * @param Institution $institution
724
     * @param RegistrationAuthorityRole $role
725
     * @param InstitutionConfiguration $institutionConfiguration
726
     */
727
    public function appointAs(
728
        Institution $institution,
729
        RegistrationAuthorityRole $role,
730
        InstitutionConfiguration $institutionConfiguration
731
    ) {
732
        $this->assertNotForgotten();
733
734
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(new ConfigurationInstitution($this->institution->getInstitution()))) {
735
            throw new DomainException(
736
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority for institution'
737
            );
738
        }
739
740
        $registrationAuthority = $this->registrationAuthorities->get($institution);
741
742
        if ($registrationAuthority->isAppointedAs($role)) {
743
            return;
744
        }
745
746
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
747
            $this->apply(new AppointedAsRaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution));
748
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
749
            $this->apply(new AppointedAsRaaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution));
750
        } else {
751
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
752
        }
753
    }
754
755
    public function retractRegistrationAuthority(Institution $institution)
756
    {
757
        $this->assertNotForgotten();
758
759
        if (!$this->registrationAuthorities->exists($institution)) {
760
            throw new DomainException(
761
                'Cannot Retract Registration Authority as the Identity is not a registration authority'
762
            );
763
        }
764
765
        $this->apply(new RegistrationAuthorityRetractedForInstitutionEvent(
766
            $this->id,
767
            $this->institution,
768
            $this->nameId,
769
            $this->commonName,
770
            $this->email,
771
            $institution
772
        ));
773
    }
774
775
    public function expressPreferredLocale(Locale $preferredLocale)
776
    {
777
        $this->assertNotForgotten();
778
779
        if ($this->preferredLocale === $preferredLocale) {
780
            return;
781
        }
782
783
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
784
    }
785
786
    public function forget()
787
    {
788
        $this->assertNotForgotten();
789
790
        if ($this->registrationAuthorities->count()) {
791
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
792
        }
793
794
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
795
    }
796
797
    public function allVettedSecondFactorsRemoved()
798
    {
799
        $this->apply(
800
            new VettedSecondFactorsAllRevokedEvent(
801
                $this->id,
802
                $this->institution
803
            )
804
        );
805
    }
806
807
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event)
808
    {
809
        $this->id = $event->identityId;
810
        $this->institution = $event->identityInstitution;
811
        $this->nameId = $event->nameId;
812
        $this->commonName = $event->commonName;
813
        $this->email = $event->email;
814
        $this->preferredLocale = $event->preferredLocale;
815
        $this->forgotten = false;
816
817
        $this->unverifiedSecondFactors = new SecondFactorCollection();
818
        $this->verifiedSecondFactors = new SecondFactorCollection();
819
        $this->vettedSecondFactors = new SecondFactorCollection();
820
        $this->registrationAuthorities = new RegistrationAuthorityCollection();
821
    }
822
823
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event)
824
    {
825
        $this->commonName = $event->commonName;
826
    }
827
828
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event)
829
    {
830
        $this->email = $event->email;
831
    }
832
833
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event)
834
    {
835
        $secondFactor = VettedSecondFactor::create(
836
            $event->secondFactorId,
837
            $this,
838
            new SecondFactorType('yubikey'),
839
            $event->yubikeyPublicId
840
        );
841
842
        $this->vettedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
843
    }
844
845
    protected function applyYubikeyPossessionProvenEvent(YubikeyPossessionProvenEvent $event)
846
    {
847
        $secondFactor = UnverifiedSecondFactor::create(
848
            $event->secondFactorId,
849
            $this,
850
            new SecondFactorType('yubikey'),
851
            $event->yubikeyPublicId,
852
            $event->emailVerificationWindow,
853
            $event->emailVerificationNonce
854
        );
855
856
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
857
    }
858
859
    protected function applyYubikeyPossessionProvenAndVerifiedEvent(YubikeyPossessionProvenAndVerifiedEvent $event)
860
    {
861
        $secondFactor = VerifiedSecondFactor::create(
862
            $event->secondFactorId,
863
            $this,
864
            new SecondFactorType('yubikey'),
865
            $event->yubikeyPublicId,
866
            $event->registrationRequestedAt,
867
            $event->registrationCode
868
        );
869
870
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
871
    }
872
873
    protected function applyPhonePossessionProvenEvent(PhonePossessionProvenEvent $event)
874
    {
875
        $secondFactor = UnverifiedSecondFactor::create(
876
            $event->secondFactorId,
877
            $this,
878
            new SecondFactorType('sms'),
879
            $event->phoneNumber,
880
            $event->emailVerificationWindow,
881
            $event->emailVerificationNonce
882
        );
883
884
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
885
    }
886
887
    protected function applyPhonePossessionProvenAndVerifiedEvent(PhonePossessionProvenAndVerifiedEvent $event)
888
    {
889
        $secondFactor = VerifiedSecondFactor::create(
890
            $event->secondFactorId,
891
            $this,
892
            new SecondFactorType('sms'),
893
            $event->phoneNumber,
894
            $event->registrationRequestedAt,
895
            $event->registrationCode
896
        );
897
898
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
899
    }
900
901
    protected function applyGssfPossessionProvenEvent(GssfPossessionProvenEvent $event)
902
    {
903
        $secondFactor = UnverifiedSecondFactor::create(
904
            $event->secondFactorId,
905
            $this,
906
            new SecondFactorType((string)$event->stepupProvider),
907
            $event->gssfId,
908
            $event->emailVerificationWindow,
909
            $event->emailVerificationNonce
910
        );
911
912
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
913
    }
914
915
    protected function applyGssfPossessionProvenAndVerifiedEvent(GssfPossessionProvenAndVerifiedEvent $event)
916
    {
917
        $secondFactor = VerifiedSecondFactor::create(
918
            $event->secondFactorId,
919
            $this,
920
            new SecondFactorType((string)$event->stepupProvider),
921
            $event->gssfId,
922
            $event->registrationRequestedAt,
923
            $event->registrationCode
924
        );
925
926
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
927
    }
928
929
    protected function applyU2fDevicePossessionProvenEvent(U2fDevicePossessionProvenEvent $event)
930
    {
931
        $secondFactor = UnverifiedSecondFactor::create(
932
            $event->secondFactorId,
933
            $this,
934
            new SecondFactorType('u2f'),
935
            $event->keyHandle,
936
            $event->emailVerificationWindow,
937
            $event->emailVerificationNonce
938
        );
939
940
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
941
    }
942
943
    protected function applyU2fDevicePossessionProvenAndVerifiedEvent(U2fDevicePossessionProvenAndVerifiedEvent $event)
944
    {
945
        $secondFactor = VerifiedSecondFactor::create(
946
            $event->secondFactorId,
947
            $this,
948
            new SecondFactorType('u2f'),
949
            $event->keyHandle,
950
            $event->registrationRequestedAt,
951
            $event->registrationCode
952
        );
953
954
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
955
    }
956
957
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event)
958
    {
959
        $secondFactorId = (string)$event->secondFactorId;
960
961
        /** @var UnverifiedSecondFactor $unverified */
962
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
963
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
964
965
        $this->unverifiedSecondFactors->remove($secondFactorId);
966
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
967
    }
968
969
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event)
970
    {
971
        $secondFactorId = (string)$event->secondFactorId;
972
973
        /** @var VerifiedSecondFactor $verified */
974
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
975
        $vetted = $verified->asVetted();
976
977
        $this->verifiedSecondFactors->remove($secondFactorId);
978
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
979
    }
980
981
    protected function applySecondFactorVettedWithoutTokenProofOfPossession(SecondFactorVettedWithoutTokenProofOfPossession $event)
982
    {
983
        $secondFactorId = (string)$event->secondFactorId;
984
985
        /** @var VerifiedSecondFactor $verified */
986
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
987
        $vetted = $verified->asVetted();
988
989
        $this->verifiedSecondFactors->remove($secondFactorId);
990
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
991
    }
992
993
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event)
994
    {
995
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
996
    }
997
998
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
999
        CompliedWithUnverifiedSecondFactorRevocationEvent $event
1000
    ) {
1001
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
1002
    }
1003
1004
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event)
1005
    {
1006
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
1007
    }
1008
1009
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
1010
        CompliedWithVerifiedSecondFactorRevocationEvent $event
1011
    ) {
1012
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
1013
    }
1014
1015
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event)
1016
    {
1017
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
1018
    }
1019
1020
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
1021
        CompliedWithVettedSecondFactorRevocationEvent $event
1022
    ) {
1023
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
1024
    }
1025
1026
    protected function applyIdentityAccreditedAsRaForInstitutionEvent(IdentityAccreditedAsRaForInstitutionEvent $event)
1027
    {
1028
        $this->registrationAuthorities->set($event->raInstitution, RegistrationAuthority::accreditWith(
1029
            $event->registrationAuthorityRole,
1030
            $event->location,
1031
            $event->contactInformation,
1032
            $event->raInstitution
1033
        ));
1034
    }
1035
1036
    protected function applyIdentityAccreditedAsRaaForInstitutionEvent(IdentityAccreditedAsRaaForInstitutionEvent $event)
1037
    {
1038
        $this->registrationAuthorities->set($event->raInstitution, RegistrationAuthority::accreditWith(
1039
            $event->registrationAuthorityRole,
1040
            $event->location,
1041
            $event->contactInformation,
1042
            $event->raInstitution
1043
        ));
1044
    }
1045
1046
    protected function applyRegistrationAuthorityInformationAmendedForInstitutionEvent(
1047
        RegistrationAuthorityInformationAmendedForInstitutionEvent $event
1048
    ) {
1049
        $this->registrationAuthorities->get($event->raInstitution)->amendInformation($event->location, $event->contactInformation);
1050
    }
1051
1052
    protected function applyAppointedAsRaaForInstitutionEvent(AppointedAsRaaForInstitutionEvent $event)
1053
    {
1054
        $this->registrationAuthorities->get($event->raInstitution)->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1055
    }
1056
1057
    protected function applyRegistrationAuthorityRetractedForInstitutionEvent(RegistrationAuthorityRetractedForInstitutionEvent $event)
1058
    {
1059
        $this->registrationAuthorities->remove($event->raInstitution);
1060
    }
1061
1062
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event)
1063
    {
1064
        $this->preferredLocale = $event->preferredLocale;
1065
    }
1066
1067
    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...
1068
    {
1069
        $this->commonName = CommonName::unknown();
1070
        $this->email = Email::unknown();
1071
        $this->forgotten = true;
1072
    }
1073
1074
    /**
1075
     * This method is kept to be backwards compatible for changes before FGA
1076
     *
1077
     * @param AppointedAsRaEvent $event
1078
     */
1079
    protected function applyAppointedAsRaEvent(AppointedAsRaEvent $event)
1080
    {
1081
        $this->registrationAuthorities->get($event->identityInstitution)
1082
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1083
    }
1084
1085
    /**
1086
     * This method is kept to be backwards compatible for changes before FGA
1087
     *
1088
     * @param AppointedAsRaaEvent $event
1089
     */
1090
    protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $event)
1091
    {
1092
        $this->registrationAuthorities->get($event->identityInstitution)
1093
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1094
    }
1095
1096
    /**
1097
     * This method is kept to be backwards compatible for changes before FGA
1098
     *
1099
     * @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...
1100
     */
1101
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event)
1102
    {
1103
        $this->registrationAuthorities->set($event->identityInstitution, RegistrationAuthority::accreditWith(
1104
            $event->registrationAuthorityRole,
1105
            $event->location,
1106
            $event->contactInformation,
1107
            $event->identityInstitution
1108
        ));
1109
    }
1110
1111
    /**
1112
     * This method is kept to be backwards compatible for changes before FGA
1113
     *
1114
     * @param IdentityAccreditedAsRaaEvent $event
1115
     */
1116
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event)
1117
    {
1118
        $this->registrationAuthorities->set($event->identityInstitution, RegistrationAuthority::accreditWith(
1119
            $event->registrationAuthorityRole,
1120
            $event->location,
1121
            $event->contactInformation,
1122
            $event->identityInstitution
1123
        ));
1124
    }
1125
1126
    /**
1127
     * This method is kept to be backwards compatible for changes before FGA
1128
     *
1129
     * @param AppointedAsRaForInstitutionEvent $event
1130
     */
1131
    protected function applyAppointedAsRaForInstitutionEvent(AppointedAsRaForInstitutionEvent $event)
1132
    {
1133
        $this->registrationAuthorities->get($event->identityInstitution)
1134
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1135
    }
1136
1137
    /**
1138
     * This method is kept to be backwards compatible for changes before FGA
1139
     *
1140
     * @param RegistrationAuthorityInformationAmendedEvent $event
1141
     */
1142
    protected function applyRegistrationAuthorityInformationAmendedEvent(
1143
        RegistrationAuthorityInformationAmendedEvent $event
1144
    ) {
1145
        $this->registrationAuthorities->get($event->identityInstitution)->amendInformation($event->location, $event->contactInformation);
1146
    }
1147
1148
    /**
1149
     * This method is kept to be backwards compatible for changes before FGA
1150
     *
1151
     * @param RegistrationAuthorityRetractedEvent $event
1152
     */
1153
    protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $event)
1154
    {
1155
        $this->registrationAuthorities->remove($event->identityInstitution);
1156
    }
1157
1158
1159
    public function getAggregateRootId(): string
1160
    {
1161
        return $this->id->getIdentityId();
1162
    }
1163
1164
    protected function getChildEntities(): array
1165
    {
1166
        return array_merge(
1167
            $this->unverifiedSecondFactors->getValues(),
1168
            $this->verifiedSecondFactors->getValues(),
1169
            $this->vettedSecondFactors->getValues(),
1170
            $this->registrationAuthorities->getValues()
1171
        );
1172
    }
1173
1174
    /**
1175
     * @throws DomainException
1176
     */
1177
    private function assertNotForgotten()
1178
    {
1179
        if ($this->forgotten) {
1180
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
1181
        }
1182
    }
1183
1184
    /**
1185
     * @throws DomainException
1186
     */
1187
    private function assertUserMayAddSecondFactor()
1188
    {
1189
        if (count($this->unverifiedSecondFactors) +
1190
            count($this->verifiedSecondFactors) +
1191
            count($this->vettedSecondFactors) >= $this->maxNumberOfTokens
1192
        ) {
1193
            throw new DomainException(
1194
                sprintf('User may not have more than %d token(s)', $this->maxNumberOfTokens)
1195
            );
1196
        }
1197
    }
1198
1199
    public function getId()
1200
    {
1201
        return $this->id;
1202
    }
1203
1204
    /**
1205
     * @return NameId
1206
     */
1207
    public function getNameId()
1208
    {
1209
        return $this->nameId;
1210
    }
1211
1212
    /**
1213
     * @return Institution
1214
     */
1215
    public function getInstitution()
1216
    {
1217
        return $this->institution;
1218
    }
1219
1220
    public function getCommonName()
1221
    {
1222
        return $this->commonName;
1223
    }
1224
1225
    public function getEmail()
1226
    {
1227
        return $this->email;
1228
    }
1229
1230
    public function getPreferredLocale()
1231
    {
1232
        return $this->preferredLocale;
1233
    }
1234
1235
    /**
1236
     * @param SecondFactorId $secondFactorId
1237
     * @return VerifiedSecondFactor|null
1238
     */
1239
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId)
1240
    {
1241
        return $this->verifiedSecondFactors->get((string)$secondFactorId);
1242
    }
1243
1244
    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...
1245
    {
1246
        return $this->vettedSecondFactors->get((string)$secondFactorId);
1247
    }
1248
}
1249