Completed
Pull Request — develop (#285)
by
unknown
04:32 queued 02:13
created

Identity::applyU2fDevicePossessionProvenEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 13
Ratio 100 %

Importance

Changes 0
Metric Value
dl 13
loc 13
rs 9.8333
c 0
b 0
f 0
cc 1
nc 1
nop 1

1 Method

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