Completed
Push — feature/fix-token-ra-candiate-... ( 57b9ef...288fa0 )
by
unknown
02:33
created

Identity::allVettedSecondFactorsRemoved()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\Identity\Api\Identity as IdentityApi;
27
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
28
use Surfnet\Stepup\Identity\Entity\RegistrationAuthorityCollection;
29
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
30
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
31
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
32
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
33
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
34
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
35
use Surfnet\Stepup\Identity\Event\AppointedAsRaaForInstitutionEvent;
36
use Surfnet\Stepup\Identity\Event\AppointedAsRaForInstitutionEvent;
37
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
38
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
39
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
40
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
41
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenAndVerifiedEvent;
42
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
43
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
44
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaForInstitutionEvent;
45
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
46
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaForInstitutionEvent;
47
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
48
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
49
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
50
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
51
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
52
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenAndVerifiedEvent;
53
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
54
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
55
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedForInstitutionEvent;
56
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
57
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedForInstitutionEvent;
58
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
59
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenAndVerifiedEvent;
60
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
61
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
62
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
63
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
64
use Surfnet\Stepup\Identity\Event\VettedSecondFactorsAllRevokedEvent;
65
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenAndVerifiedEvent;
66
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
67
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
68
use Surfnet\Stepup\Identity\Value\CommonName;
69
use Surfnet\Stepup\Identity\Value\ContactInformation;
70
use Surfnet\Stepup\Identity\Value\DocumentNumber;
71
use Surfnet\Stepup\Identity\Value\Email;
72
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow;
73
use Surfnet\Stepup\Identity\Value\GssfId;
74
use Surfnet\Stepup\Identity\Value\IdentityId;
75
use Surfnet\Stepup\Identity\Value\Institution;
76
use Surfnet\Stepup\Identity\Value\Locale;
77
use Surfnet\Stepup\Identity\Value\Location;
78
use Surfnet\Stepup\Identity\Value\NameId;
79
use Surfnet\Stepup\Identity\Value\PhoneNumber;
80
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole;
81
use Surfnet\Stepup\Identity\Value\SecondFactorId;
82
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
83
use Surfnet\Stepup\Identity\Value\StepupProvider;
84
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
85
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
86
use Surfnet\Stepup\Token\TokenGenerator;
87
use Surfnet\StepupBundle\Security\OtpGenerator;
88
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
89
use Surfnet\StepupBundle\Value\SecondFactorType;
90
91
/**
92
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
93
 * @SuppressWarnings(PHPMD.TooManyMethods)
94
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
95
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
96
 * @SuppressWarnings(PHPMD.ExcessiveClassLength)
97
 */
98
class Identity extends EventSourcedAggregateRoot implements IdentityApi
99
{
100
    /**
101
     * @var IdentityId
102
     */
103
    private $id;
104
105
    /**
106
     * @var Institution
107
     */
108
    private $institution;
109
110
    /**
111
     * @var NameId
112
     */
113
    private $nameId;
114
115
    /**
116
     * @var \Surfnet\Stepup\Identity\Value\CommonName
117
     */
118
    private $commonName;
119
120
    /**
121
     * @var \Surfnet\Stepup\Identity\Value\Email
122
     */
123
    private $email;
124
125
    /**
126
     * @var SecondFactorCollection|UnverifiedSecondFactor[]
127
     */
128
    private $unverifiedSecondFactors;
129
130
    /**
131
     * @var SecondFactorCollection|VerifiedSecondFactor[]
132
     */
133
    private $verifiedSecondFactors;
134
135
    /**
136
     * @var SecondFactorCollection|VettedSecondFactor[]
137
     */
138
    private $vettedSecondFactors;
139
140
    /**
141
     * @var RegistrationAuthorityCollection
142
     */
143
    private $registrationAuthorities;
144
145
    /**
146
     * @var Locale
147
     */
148
    private $preferredLocale;
149
150
    /**
151
     * @var boolean
152
     */
153
    private $forgotten;
154
155
    /**
156
     * @var int
157
     */
158
    private $maxNumberOfTokens = 1;
159
160
    public static function create(
161
        IdentityId $id,
162
        Institution $institution,
163
        NameId $nameId,
164
        CommonName $commonName,
165
        Email $email,
166
        Locale $preferredLocale
167
    ) {
168
        $identity = new self();
169
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
170
171
        return $identity;
172
    }
173
174
    final public function __construct()
175
    {
176
    }
177
178
    public function rename(CommonName $commonName)
179
    {
180
        $this->assertNotForgotten();
181
182
        if ($this->commonName->equals($commonName)) {
183
            return;
184
        }
185
186
        $this->commonName = $commonName;
187
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
188
    }
189
190
    public function changeEmail(Email $email)
191
    {
192
        $this->assertNotForgotten();
193
194
        if ($this->email->equals($email)) {
195
            return;
196
        }
197
198
        $this->email = $email;
199
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
200
    }
201
202
    /**
203
     * @param int $numberOfTokens
204
     */
205
    public function setMaxNumberOfTokens($numberOfTokens)
206
    {
207
        $this->maxNumberOfTokens = $numberOfTokens;
208
    }
209
210
    public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId)
211
    {
212
        $this->assertNotForgotten();
213
        $this->assertUserMayAddSecondFactor();
214
215
        $this->apply(
216
            new YubikeySecondFactorBootstrappedEvent(
217
                $this->id,
218
                $this->nameId,
219
                $this->institution,
220
                $this->commonName,
221
                $this->email,
222
                $this->preferredLocale,
223
                $secondFactorId,
224
                $yubikeyPublicId
225
            )
226
        );
227
    }
228
229 View Code Duplication
    public function provePossessionOfYubikey(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
        SecondFactorId $secondFactorId,
231
        YubikeyPublicId $yubikeyPublicId,
232
        $emailVerificationRequired,
233
        EmailVerificationWindow $emailVerificationWindow
234
    ) {
235
        $this->assertNotForgotten();
236
        $this->assertUserMayAddSecondFactor();
237
238
        if ($emailVerificationRequired) {
239
            $emailVerificationNonce = TokenGenerator::generateNonce();
240
241
            $this->apply(
242
                new YubikeyPossessionProvenEvent(
243
                    $this->id,
244
                    $this->institution,
245
                    $secondFactorId,
246
                    $yubikeyPublicId,
247
                    $emailVerificationRequired,
248
                    $emailVerificationWindow,
249
                    $emailVerificationNonce,
250
                    $this->commonName,
251
                    $this->email,
252
                    $this->preferredLocale
253
                )
254
            );
255
        } else {
256
            $this->apply(
257
                new YubikeyPossessionProvenAndVerifiedEvent(
258
                    $this->id,
259
                    $this->institution,
260
                    $secondFactorId,
261
                    $yubikeyPublicId,
262
                    $this->commonName,
263
                    $this->email,
264
                    $this->preferredLocale,
265
                    DateTime::now(),
266
                    OtpGenerator::generate(8)
267
                )
268
            );
269
        }
270
    }
271
272 View Code Duplication
    public function provePossessionOfPhone(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
        SecondFactorId $secondFactorId,
274
        PhoneNumber $phoneNumber,
275
        $emailVerificationRequired,
276
        EmailVerificationWindow $emailVerificationWindow
277
    ) {
278
        $this->assertNotForgotten();
279
        $this->assertUserMayAddSecondFactor();
280
281
        if ($emailVerificationRequired) {
282
            $emailVerificationNonce = TokenGenerator::generateNonce();
283
284
            $this->apply(
285
                new PhonePossessionProvenEvent(
286
                    $this->id,
287
                    $this->institution,
288
                    $secondFactorId,
289
                    $phoneNumber,
290
                    $emailVerificationRequired,
291
                    $emailVerificationWindow,
292
                    $emailVerificationNonce,
293
                    $this->commonName,
294
                    $this->email,
295
                    $this->preferredLocale
296
                )
297
            );
298
        } else {
299
            $this->apply(
300
                new PhonePossessionProvenAndVerifiedEvent(
301
                    $this->id,
302
                    $this->institution,
303
                    $secondFactorId,
304
                    $phoneNumber,
305
                    $this->commonName,
306
                    $this->email,
307
                    $this->preferredLocale,
308
                    DateTime::now(),
309
                    OtpGenerator::generate(8)
310
                )
311
            );
312
        }
313
    }
314
315 View Code Duplication
    public function provePossessionOfGssf(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
        SecondFactorId $secondFactorId,
317
        StepupProvider $provider,
318
        GssfId $gssfId,
319
        $emailVerificationRequired,
320
        EmailVerificationWindow $emailVerificationWindow
321
    ) {
322
        $this->assertNotForgotten();
323
        $this->assertUserMayAddSecondFactor();
324
325
        if ($emailVerificationRequired) {
326
            $emailVerificationNonce = TokenGenerator::generateNonce();
327
328
            $this->apply(
329
                new GssfPossessionProvenEvent(
330
                    $this->id,
331
                    $this->institution,
332
                    $secondFactorId,
333
                    $provider,
334
                    $gssfId,
335
                    $emailVerificationRequired,
336
                    $emailVerificationWindow,
337
                    $emailVerificationNonce,
338
                    $this->commonName,
339
                    $this->email,
340
                    $this->preferredLocale
341
                )
342
            );
343
        } else {
344
            $this->apply(
345
                new GssfPossessionProvenAndVerifiedEvent(
346
                    $this->id,
347
                    $this->institution,
348
                    $secondFactorId,
349
                    $provider,
350
                    $gssfId,
351
                    $this->commonName,
352
                    $this->email,
353
                    $this->preferredLocale,
354
                    DateTime::now(),
355
                    OtpGenerator::generate(8)
356
                )
357
            );
358
        }
359
    }
360
361 View Code Duplication
    public function provePossessionOfU2fDevice(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
362
        SecondFactorId $secondFactorId,
363
        U2fKeyHandle $keyHandle,
364
        $emailVerificationRequired,
365
        EmailVerificationWindow $emailVerificationWindow
366
    ) {
367
        $this->assertNotForgotten();
368
        $this->assertUserMayAddSecondFactor();
369
370
        if ($emailVerificationRequired) {
371
            $emailVerificationNonce = TokenGenerator::generateNonce();
372
373
            $this->apply(
374
                new U2fDevicePossessionProvenEvent(
375
                    $this->id,
376
                    $this->institution,
377
                    $secondFactorId,
378
                    $keyHandle,
379
                    $emailVerificationRequired,
380
                    $emailVerificationWindow,
381
                    $emailVerificationNonce,
382
                    $this->commonName,
383
                    $this->email,
384
                    $this->preferredLocale
385
                )
386
            );
387
        } else {
388
            $this->apply(
389
                new U2fDevicePossessionProvenAndVerifiedEvent(
390
                    $this->id,
391
                    $this->institution,
392
                    $secondFactorId,
393
                    $keyHandle,
394
                    $this->commonName,
395
                    $this->email,
396
                    $this->preferredLocale,
397
                    DateTime::now(),
398
                    OtpGenerator::generate(8)
399
                )
400
            );
401
        }
402
    }
403
404
    public function verifyEmail($verificationNonce)
405
    {
406
        $this->assertNotForgotten();
407
408
        $secondFactorToVerify = null;
409
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
410
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
411
            if ($secondFactor->hasNonce($verificationNonce)) {
412
                $secondFactorToVerify = $secondFactor;
413
            }
414
        }
415
416
        if (!$secondFactorToVerify) {
417
            throw new DomainException(
418
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
419
            );
420
        }
421
422
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
423
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
424
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
425
        }
426
427
        $secondFactorToVerify->verifyEmail();
428
    }
429
430
    public function vetSecondFactor(
431
        IdentityApi $registrant,
432
        SecondFactorId $registrantsSecondFactorId,
433
        SecondFactorType $registrantsSecondFactorType,
434
        SecondFactorIdentifier $registrantsSecondFactorIdentifier,
435
        $registrationCode,
436
        DocumentNumber $documentNumber,
437
        $identityVerified,
438
        SecondFactorTypeService $secondFactorTypeService
439
    ) {
440
        $this->assertNotForgotten();
441
442
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
443
        $secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa($secondFactorTypeService);
444
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
445
446
        if ($registrantsSecondFactor === null) {
447
            throw new DomainException(
448
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId)
449
            );
450
        }
451
452
        if ($secondFactorWithHighestLoa === null) {
453
            throw new DomainException(
454
                sprintf(
455
                    'Vetting failed: authority %s has %d vetted second factors!',
456
                    $this->id,
457
                    count($this->vettedSecondFactors)
458
                )
459
            );
460
        }
461
462
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo(
463
            $registrantsSecondFactor,
464
            $secondFactorTypeService
465
        )) {
466
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
467
        }
468
469
        if (!$identityVerified) {
470
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
471
        }
472
473
        $registrant->complyWithVettingOfSecondFactor(
474
            $registrantsSecondFactorId,
475
            $registrantsSecondFactorType,
476
            $registrantsSecondFactorIdentifier,
477
            $registrationCode,
478
            $documentNumber
479
        );
480
    }
481
482
    public function complyWithVettingOfSecondFactor(
483
        SecondFactorId $secondFactorId,
484
        SecondFactorType $secondFactorType,
485
        SecondFactorIdentifier $secondFactorIdentifier,
486
        $registrationCode,
487
        DocumentNumber $documentNumber
488
    ) {
489
        $this->assertNotForgotten();
490
491
        $secondFactorToVet = null;
492
        foreach ($this->verifiedSecondFactors as $secondFactor) {
493
            /** @var VerifiedSecondFactor $secondFactor */
494
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
495
                $secondFactorToVet = $secondFactor;
496
            }
497
        }
498
499
        if (!$secondFactorToVet) {
500
            throw new DomainException(
501
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
502
                'and second factor identifier'
503
            );
504
        }
505
506
        if (!$secondFactorToVet->canBeVettedNow()) {
507
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
508
        }
509
510
        $secondFactorToVet->vet($documentNumber);
511
    }
512
513
    public function revokeSecondFactor(SecondFactorId $secondFactorId)
514
    {
515
        $this->assertNotForgotten();
516
517
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
518
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
519
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
520
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
521
        /** @var VettedSecondFactor|null $vettedSecondFactor */
522
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
523
524
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
525
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
526
        }
527
528
        if ($unverifiedSecondFactor) {
529
            $unverifiedSecondFactor->revoke();
530
531
            return;
532
        }
533
534
        if ($verifiedSecondFactor) {
535
            $verifiedSecondFactor->revoke();
536
537
            return;
538
        }
539
540
        $vettedSecondFactor->revoke();
541
542
        if ($this->vettedSecondFactors->isEmpty()) {
543
            $this->allVettedSecondFactorsRemoved();
544
        }
545
    }
546
547
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId)
548
    {
549
        $this->assertNotForgotten();
550
551
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
552
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
553
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
554
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
555
        /** @var VettedSecondFactor|null $vettedSecondFactor */
556
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
557
558
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
559
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
560
        }
561
562
        if ($unverifiedSecondFactor) {
563
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
564
565
            return;
566
        }
567
568
        if ($verifiedSecondFactor) {
569
            $verifiedSecondFactor->complyWithRevocation($authorityId);
570
571
            return;
572
        }
573
574
        $vettedSecondFactor->complyWithRevocation($authorityId);
575
576
        if ($this->vettedSecondFactors->isEmpty()) {
577
            $this->allVettedSecondFactorsRemoved();
578
        }
579
    }
580
581
    /**
582
     * @param RegistrationAuthorityRole $role
583
     * @param Institution $institution
584
     * @param Location $location
585
     * @param ContactInformation $contactInformation
586
     * @param InstitutionConfiguration $institutionConfiguration
587
     * @return void
588
     */
589
    public function accreditWith(
590
        RegistrationAuthorityRole $role,
591
        Institution $institution,
592
        Location $location,
593
        ContactInformation $contactInformation,
594
        InstitutionConfiguration $institutionConfiguration
595
    ) {
596
        $this->assertNotForgotten();
597
598
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(new ConfigurationInstitution($this->institution->getInstitution()))) {
599
            throw new DomainException('An Identity may only be accredited by configured institutions.');
600
        }
601
602
        if (!$this->vettedSecondFactors->count()) {
603
            throw new DomainException(
604
                'An Identity must have at least one vetted second factor before it can be accredited'
605
            );
606
        }
607
608
        if ($this->registrationAuthorities->exists($institution)) {
609
            throw new DomainException('Cannot accredit Identity as it has already been accredited for institution');
610
        }
611
612
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
613
            $this->apply(new IdentityAccreditedAsRaForInstitutionEvent(
614
                $this->id,
615
                $this->nameId,
616
                $this->institution,
617
                $role,
618
                $location,
619
                $contactInformation,
620
                $institution
621
            ));
622 View Code Duplication
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
623
            $this->apply(new IdentityAccreditedAsRaaForInstitutionEvent(
624
                $this->id,
625
                $this->nameId,
626
                $this->institution,
627
                $role,
628
                $location,
629
                $contactInformation,
630
                $institution
631
            ));
632
        } else {
633
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
634
        }
635
    }
636
637
    public function amendRegistrationAuthorityInformation(Institution $institution, Location $location, ContactInformation $contactInformation)
638
    {
639
        $this->assertNotForgotten();
640
641
        if (!$this->registrationAuthorities->exists($institution)) {
642
            throw new DomainException(
643
                'Cannot amend registration authority information: identity is not a registration authority for institution'
644
            );
645
        }
646
647
        $this->apply(
648
            new RegistrationAuthorityInformationAmendedForInstitutionEvent(
649
                $this->id,
650
                $this->institution,
651
                $this->nameId,
652
                $location,
653
                $contactInformation,
654
                $institution
655
            )
656
        );
657
    }
658
659
    /**
660
     * This method will appoint an institution to become ra or raa for another institution
661
     *
662
     * @param Institution $institution
663
     * @param RegistrationAuthorityRole $role
664
     * @param InstitutionConfiguration $institutionConfiguration
665
     */
666
    public function appointAs(
667
        Institution $institution,
668
        RegistrationAuthorityRole $role,
669
        InstitutionConfiguration $institutionConfiguration
670
    ) {
671
        $this->assertNotForgotten();
672
673
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(new ConfigurationInstitution($this->institution->getInstitution()))) {
674
            throw new DomainException(
675
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority for institution'
676
            );
677
        }
678
679
        $registrationAuthority = $this->registrationAuthorities->get($institution);
680
681
        if ($registrationAuthority->isAppointedAs($role)) {
682
            return;
683
        }
684
685
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
686
            $this->apply(new AppointedAsRaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution));
687 View Code Duplication
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
688
            $this->apply(new AppointedAsRaaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution));
689
        } else {
690
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
691
        }
692
    }
693
694
    public function retractRegistrationAuthority(Institution $institution)
695
    {
696
        $this->assertNotForgotten();
697
698
        if (!$this->registrationAuthorities->exists($institution)) {
699
            throw new DomainException(
700
                'Cannot Retract Registration Authority as the Identity is not a registration authority'
701
            );
702
        }
703
704
        $this->apply(new RegistrationAuthorityRetractedForInstitutionEvent(
705
            $this->id,
706
            $this->institution,
707
            $this->nameId,
708
            $this->commonName,
709
            $this->email,
710
            $institution
711
        ));
712
    }
713
714
    public function expressPreferredLocale(Locale $preferredLocale)
715
    {
716
        $this->assertNotForgotten();
717
718
        if ($this->preferredLocale === $preferredLocale) {
719
            return;
720
        }
721
722
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
723
    }
724
725
    public function forget()
726
    {
727
        $this->assertNotForgotten();
728
729
        if ($this->registrationAuthorities->count()) {
730
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
731
        }
732
733
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
734
    }
735
736
    public function allVettedSecondFactorsRemoved()
737
    {
738
        $this->apply(
739
            new VettedSecondFactorsAllRevokedEvent(
740
                $this->id,
741
                $this->institution
742
            )
743
        );
744
    }
745
746
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event)
747
    {
748
        $this->id = $event->identityId;
749
        $this->institution = $event->identityInstitution;
750
        $this->nameId = $event->nameId;
751
        $this->commonName = $event->commonName;
752
        $this->email = $event->email;
753
        $this->preferredLocale = $event->preferredLocale;
754
        $this->forgotten = false;
755
756
        $this->unverifiedSecondFactors = new SecondFactorCollection();
757
        $this->verifiedSecondFactors = new SecondFactorCollection();
758
        $this->vettedSecondFactors = new SecondFactorCollection();
759
        $this->registrationAuthorities = new RegistrationAuthorityCollection();
760
    }
761
762
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event)
763
    {
764
        $this->commonName = $event->commonName;
765
    }
766
767
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event)
768
    {
769
        $this->email = $event->email;
770
    }
771
772
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event)
773
    {
774
        $secondFactor = VettedSecondFactor::create(
775
            $event->secondFactorId,
776
            $this,
777
            new SecondFactorType('yubikey'),
778
            $event->yubikeyPublicId
779
        );
780
781
        $this->vettedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
782
    }
783
784 View Code Duplication
    protected function applyYubikeyPossessionProvenEvent(YubikeyPossessionProvenEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
785
    {
786
        $secondFactor = UnverifiedSecondFactor::create(
787
            $event->secondFactorId,
788
            $this,
789
            new SecondFactorType('yubikey'),
790
            $event->yubikeyPublicId,
791
            $event->emailVerificationWindow,
792
            $event->emailVerificationNonce
793
        );
794
795
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
796
    }
797
798 View Code Duplication
    protected function applyYubikeyPossessionProvenAndVerifiedEvent(YubikeyPossessionProvenAndVerifiedEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
799
    {
800
        $secondFactor = VerifiedSecondFactor::create(
801
            $event->secondFactorId,
802
            $this,
803
            new SecondFactorType('yubikey'),
804
            $event->yubikeyPublicId,
805
            $event->registrationRequestedAt,
806
            $event->registrationCode
807
        );
808
809
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
810
    }
811
812 View Code Duplication
    protected function applyPhonePossessionProvenEvent(PhonePossessionProvenEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
813
    {
814
        $secondFactor = UnverifiedSecondFactor::create(
815
            $event->secondFactorId,
816
            $this,
817
            new SecondFactorType('sms'),
818
            $event->phoneNumber,
819
            $event->emailVerificationWindow,
820
            $event->emailVerificationNonce
821
        );
822
823
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
824
    }
825
826 View Code Duplication
    protected function applyPhonePossessionProvenAndVerifiedEvent(PhonePossessionProvenAndVerifiedEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
827
    {
828
        $secondFactor = VerifiedSecondFactor::create(
829
            $event->secondFactorId,
830
            $this,
831
            new SecondFactorType('sms'),
832
            $event->phoneNumber,
833
            $event->registrationRequestedAt,
834
            $event->registrationCode
835
        );
836
837
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
838
    }
839
840 View Code Duplication
    protected function applyGssfPossessionProvenEvent(GssfPossessionProvenEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
841
    {
842
        $secondFactor = UnverifiedSecondFactor::create(
843
            $event->secondFactorId,
844
            $this,
845
            new SecondFactorType((string)$event->stepupProvider),
846
            $event->gssfId,
847
            $event->emailVerificationWindow,
848
            $event->emailVerificationNonce
849
        );
850
851
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
852
    }
853
854 View Code Duplication
    protected function applyGssfPossessionProvenAndVerifiedEvent(GssfPossessionProvenAndVerifiedEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
855
    {
856
        $secondFactor = VerifiedSecondFactor::create(
857
            $event->secondFactorId,
858
            $this,
859
            new SecondFactorType((string)$event->stepupProvider),
860
            $event->gssfId,
861
            $event->registrationRequestedAt,
862
            $event->registrationCode
863
        );
864
865
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
866
    }
867
868 View Code Duplication
    protected function applyU2fDevicePossessionProvenEvent(U2fDevicePossessionProvenEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
869
    {
870
        $secondFactor = UnverifiedSecondFactor::create(
871
            $event->secondFactorId,
872
            $this,
873
            new SecondFactorType('u2f'),
874
            $event->keyHandle,
875
            $event->emailVerificationWindow,
876
            $event->emailVerificationNonce
877
        );
878
879
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
880
    }
881
882 View Code Duplication
    protected function applyU2fDevicePossessionProvenAndVerifiedEvent(U2fDevicePossessionProvenAndVerifiedEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
883
    {
884
        $secondFactor = VerifiedSecondFactor::create(
885
            $event->secondFactorId,
886
            $this,
887
            new SecondFactorType('u2f'),
888
            $event->keyHandle,
889
            $event->registrationRequestedAt,
890
            $event->registrationCode
891
        );
892
893
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
894
    }
895
896
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event)
897
    {
898
        $secondFactorId = (string)$event->secondFactorId;
899
900
        /** @var UnverifiedSecondFactor $unverified */
901
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
902
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
903
904
        $this->unverifiedSecondFactors->remove($secondFactorId);
905
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
906
    }
907
908
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event)
909
    {
910
        $secondFactorId = (string)$event->secondFactorId;
911
912
        /** @var VerifiedSecondFactor $verified */
913
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
914
        $vetted = $verified->asVetted();
915
916
        $this->verifiedSecondFactors->remove($secondFactorId);
917
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
918
    }
919
920
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event)
921
    {
922
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
923
    }
924
925
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
926
        CompliedWithUnverifiedSecondFactorRevocationEvent $event
927
    ) {
928
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
929
    }
930
931
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event)
932
    {
933
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
934
    }
935
936
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
937
        CompliedWithVerifiedSecondFactorRevocationEvent $event
938
    ) {
939
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
940
    }
941
942
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event)
943
    {
944
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
945
    }
946
947
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
948
        CompliedWithVettedSecondFactorRevocationEvent $event
949
    ) {
950
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
951
    }
952
953 View Code Duplication
    protected function applyIdentityAccreditedAsRaForInstitutionEvent(IdentityAccreditedAsRaForInstitutionEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
954
    {
955
        $this->registrationAuthorities->set($event->raInstitution, RegistrationAuthority::accreditWith(
956
            $event->registrationAuthorityRole,
957
            $event->location,
958
            $event->contactInformation,
959
            $event->raInstitution
960
        ));
961
    }
962
963 View Code Duplication
    protected function applyIdentityAccreditedAsRaaForInstitutionEvent(IdentityAccreditedAsRaaForInstitutionEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
964
    {
965
        $this->registrationAuthorities->set($event->raInstitution, RegistrationAuthority::accreditWith(
966
            $event->registrationAuthorityRole,
967
            $event->location,
968
            $event->contactInformation,
969
            $event->raInstitution
970
        ));
971
    }
972
973
    protected function applyRegistrationAuthorityInformationAmendedForInstitutionEvent(
974
        RegistrationAuthorityInformationAmendedForInstitutionEvent $event
975
    ) {
976
        $this->registrationAuthorities->get($event->raInstitution)->amendInformation($event->location, $event->contactInformation);
977
    }
978
979
    protected function applyAppointedAsRaaForInstitutionEvent(AppointedAsRaaForInstitutionEvent $event)
980
    {
981
        $this->registrationAuthorities->get($event->raInstitution)->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
982
    }
983
984
    protected function applyRegistrationAuthorityRetractedForInstitutionEvent(RegistrationAuthorityRetractedForInstitutionEvent $event)
985
    {
986
        $this->registrationAuthorities->remove($event->raInstitution);
987
    }
988
989
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event)
990
    {
991
        $this->preferredLocale = $event->preferredLocale;
992
    }
993
994
    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...
995
    {
996
        $this->commonName = CommonName::unknown();
997
        $this->email = Email::unknown();
998
        $this->forgotten = true;
999
    }
1000
1001
    /**
1002
     * This method is kept to be backwards compatible for changes before FGA
1003
     *
1004
     * @param AppointedAsRaEvent $event
1005
     */
1006
    protected function applyAppointedAsRaEvent(AppointedAsRaEvent $event)
1007
    {
1008
        $this->registrationAuthorities->get($event->identityInstitution)
1009
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1010
    }
1011
1012
    /**
1013
     * This method is kept to be backwards compatible for changes before FGA
1014
     *
1015
     * @param AppointedAsRaaEvent $event
1016
     */
1017
    protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $event)
1018
    {
1019
        $this->registrationAuthorities->get($event->identityInstitution)
1020
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1021
    }
1022
1023
    /**
1024
     * This method is kept to be backwards compatible for changes before FGA
1025
     *
1026
     * @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...
1027
     */
1028 View Code Duplication
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1029
    {
1030
        $this->registrationAuthorities->set($event->identityInstitution, RegistrationAuthority::accreditWith(
1031
            $event->registrationAuthorityRole,
1032
            $event->location,
1033
            $event->contactInformation,
1034
            $event->identityInstitution
1035
        ));
1036
    }
1037
1038
    /**
1039
     * This method is kept to be backwards compatible for changes before FGA
1040
     *
1041
     * @param IdentityAccreditedAsRaaEvent $event
1042
     */
1043 View Code Duplication
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1044
    {
1045
        $this->registrationAuthorities->set($event->identityInstitution, RegistrationAuthority::accreditWith(
1046
            $event->registrationAuthorityRole,
1047
            $event->location,
1048
            $event->contactInformation,
1049
            $event->identityInstitution
1050
        ));
1051
    }
1052
1053
    /**
1054
     * This method is kept to be backwards compatible for changes before FGA
1055
     *
1056
     * @param AppointedAsRaForInstitutionEvent $event
1057
     */
1058
    protected function applyAppointedAsRaForInstitutionEvent(AppointedAsRaForInstitutionEvent $event)
1059
    {
1060
        $this->registrationAuthorities->get($event->identityInstitution)
1061
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1062
    }
1063
1064
    /**
1065
     * This method is kept to be backwards compatible for changes before FGA
1066
     *
1067
     * @param RegistrationAuthorityInformationAmendedEvent $event
1068
     */
1069
    protected function applyRegistrationAuthorityInformationAmendedEvent(
1070
        RegistrationAuthorityInformationAmendedEvent $event
1071
    ) {
1072
        $this->registrationAuthorities->get($event->identityInstitution)->amendInformation($event->location, $event->contactInformation);
1073
    }
1074
1075
    /**
1076
     * This method is kept to be backwards compatible for changes before FGA
1077
     *
1078
     * @param RegistrationAuthorityRetractedEvent $event
1079
     */
1080
    protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $event)
1081
    {
1082
        $this->registrationAuthorities->remove($event->identityInstitution);
1083
    }
1084
1085
1086
    public function getAggregateRootId()
1087
    {
1088
        return $this->id;
1089
    }
1090
1091
    protected function getChildEntities()
1092
    {
1093
        return array_merge(
1094
            $this->unverifiedSecondFactors->getValues(),
1095
            $this->verifiedSecondFactors->getValues(),
1096
            $this->vettedSecondFactors->getValues(),
1097
            $this->registrationAuthorities->getValues()
1098
        );
1099
    }
1100
1101
    /**
1102
     * @throws DomainException
1103
     */
1104
    private function assertNotForgotten()
1105
    {
1106
        if ($this->forgotten) {
1107
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
1108
        }
1109
    }
1110
1111
    /**
1112
     * @throws DomainException
1113
     */
1114
    private function assertUserMayAddSecondFactor()
1115
    {
1116
        if (count($this->unverifiedSecondFactors) +
1117
            count($this->verifiedSecondFactors) +
1118
            count($this->vettedSecondFactors) >= $this->maxNumberOfTokens
1119
        ) {
1120
            throw new DomainException(
1121
                sprintf('User may not have more than %d token(s)', $this->maxNumberOfTokens)
1122
            );
1123
        }
1124
    }
1125
1126
    public function getId()
1127
    {
1128
        return $this->id;
1129
    }
1130
1131
    /**
1132
     * @return NameId
1133
     */
1134
    public function getNameId()
1135
    {
1136
        return $this->nameId;
1137
    }
1138
1139
    /**
1140
     * @return Institution
1141
     */
1142
    public function getInstitution()
1143
    {
1144
        return $this->institution;
1145
    }
1146
1147
    public function getCommonName()
1148
    {
1149
        return $this->commonName;
1150
    }
1151
1152
    public function getEmail()
1153
    {
1154
        return $this->email;
1155
    }
1156
1157
    public function getPreferredLocale()
1158
    {
1159
        return $this->preferredLocale;
1160
    }
1161
1162
    /**
1163
     * @param SecondFactorId $secondFactorId
1164
     * @return VerifiedSecondFactor|null
1165
     */
1166
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId)
1167
    {
1168
        return $this->verifiedSecondFactors->get((string)$secondFactorId);
1169
    }
1170
}
1171