Completed
Push — feature/fga-ra-management ( 037bdc...e11d53 )
by
unknown
16:22
created

applySAccreditedInstitutionsAddedToIdentityEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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