Completed
Push — feature/fix-token-ra-candiate-... ( 2becfa...b4a1db )
by
unknown
02:44
created

Identity::applyAppointedAsRaaEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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