Completed
Push — remote-vetting ( 17e84c )
by
unknown
02:14
created

Identity::remoteVetSecondFactor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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