Completed
Push — feature/registrants-should-not... ( 3eeedd...fcf076 )
by A.
07:17 queued 03:43
created

Identity   F

Complexity

Total Complexity 96

Size/Duplication

Total Lines 800
Duplicated Lines 19.38 %

Coupling/Cohesion

Components 1
Dependencies 38

Importance

Changes 0
Metric Value
wmc 96
lcom 1
cbo 38
dl 155
loc 800
rs 1.0434
c 0
b 0
f 0

55 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 13 1
A __construct() 0 3 1
A rename() 0 11 2
A changeEmail() 0 11 2
A bootstrapYubikeySecondFactor() 0 18 1
A provePossessionOfYubikey() 22 22 1
A provePossessionOfPhone() 22 22 1
B provePossessionOfGssf() 24 24 1
B verifyEmail() 0 25 5
A provePossessionOfU2fDevice() 22 22 1
C vetSecondFactor() 0 50 7
B complyWithVettingOfSecondFactor() 0 30 5
B revokeSecondFactor() 0 29 6
B complyWithSecondFactorRevocation() 0 29 6
B accreditWith() 10 44 6
A amendRegistrationAuthorityInformation() 0 20 2
B appointAs() 3 22 5
A retractRegistrationAuthority() 0 18 2
A expressPreferredLocale() 0 10 2
A forget() 0 10 2
A applyIdentityCreatedEvent() 0 14 1
A applyIdentityRenamedEvent() 0 4 1
A applyIdentityEmailChangedEvent() 0 4 1
A applyYubikeySecondFactorBootstrappedEvent() 0 11 1
A applyYubikeyPossessionProvenEvent() 13 13 1
A applyPhonePossessionProvenEvent() 13 13 1
A applyGssfPossessionProvenEvent() 13 13 1
A applyU2fDevicePossessionProvenEvent() 13 13 1
A applyEmailVerifiedEvent() 0 11 1
A applySecondFactorVettedEvent() 0 11 1
A applyUnverifiedSecondFactorRevokedEvent() 0 4 1
A applyCompliedWithUnverifiedSecondFactorRevocationEvent() 0 5 1
A applyVerifiedSecondFactorRevokedEvent() 0 4 1
A applyCompliedWithVerifiedSecondFactorRevocationEvent() 0 5 1
A applyVettedSecondFactorRevokedEvent() 0 4 1
A applyCompliedWithVettedSecondFactorRevocationEvent() 0 5 1
A applyIdentityAccreditedAsRaEvent() 0 8 1
A applyIdentityAccreditedAsRaaEvent() 0 8 1
A applyRegistrationAuthorityInformationAmendedEvent() 0 5 1
A applyAppointedAsRaEvent() 0 4 1
A applyAppointedAsRaaEvent() 0 4 1
A applyRegistrationAuthorityRetractedEvent() 0 4 1
A applyLocalePreferenceExpressedEvent() 0 4 1
A applyIdentityForgottenEvent() 0 6 1
A getAggregateRootId() 0 4 1
A getChildEntities() 0 8 1
A assertNotForgotten() 0 6 2
A assertUserMayAddSecondFactor() 0 9 2
A getId() 0 4 1
A getNameId() 0 4 1
A getInstitution() 0 4 1
A getCommonName() 0 4 1
A getEmail() 0 4 1
A getPreferredLocale() 0 4 1
A getVerifiedSecondFactor() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Identity often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Identity, and based on these observations, apply Extract Interface, too.

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\Exception\DomainException;
23
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
24
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
25
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
26
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
27
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
28
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
29
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
30
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
31
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
32
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
33
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
34
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
35
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
36
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
37
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
38
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
39
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
40
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
41
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
42
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
43
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
44
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
45
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
46
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
47
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
48
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
49
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
50
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
51
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
52
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
53
use Surfnet\Stepup\Identity\Value\CommonName;
54
use Surfnet\Stepup\Identity\Value\ContactInformation;
55
use Surfnet\Stepup\Identity\Value\DocumentNumber;
56
use Surfnet\Stepup\Identity\Value\Email;
57
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow;
58
use Surfnet\Stepup\Identity\Value\GssfId;
59
use Surfnet\Stepup\Identity\Value\IdentityId;
60
use Surfnet\Stepup\Identity\Value\Institution;
61
use Surfnet\Stepup\Identity\Value\Locale;
62
use Surfnet\Stepup\Identity\Value\Location;
63
use Surfnet\Stepup\Identity\Value\NameId;
64
use Surfnet\Stepup\Identity\Value\PhoneNumber;
65
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole;
66
use Surfnet\Stepup\Identity\Value\SecondFactorId;
67
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
68
use Surfnet\Stepup\Identity\Value\StepupProvider;
69
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
70
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
71
use Surfnet\Stepup\Token\TokenGenerator;
72
use Surfnet\StepupBundle\Value\SecondFactorType;
73
74
/**
75
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
76
 * @SuppressWarnings(PHPMD.TooManyMethods)
77
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
78
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
79
 */
80
class Identity extends EventSourcedAggregateRoot implements IdentityApi
81
{
82
    /**
83
     * @var IdentityId
84
     */
85
    private $id;
86
87
    /**
88
     * @var Institution
89
     */
90
    private $institution;
91
92
    /**
93
     * @var NameId
94
     */
95
    private $nameId;
96
97
    /**
98
     * @var \Surfnet\Stepup\Identity\Value\CommonName
99
     */
100
    private $commonName;
101
102
    /**
103
     * @var \Surfnet\Stepup\Identity\Value\Email
104
     */
105
    private $email;
106
107
    /**
108
     * @var SecondFactorCollection|UnverifiedSecondFactor[]
109
     */
110
    private $unverifiedSecondFactors;
111
112
    /**
113
     * @var SecondFactorCollection|VerifiedSecondFactor[]
114
     */
115
    private $verifiedSecondFactors;
116
117
    /**
118
     * @var SecondFactorCollection|VettedSecondFactor[]
119
     */
120
    private $vettedSecondFactors;
121
122
    /**
123
     * @var RegistrationAuthority
124
     */
125
    private $registrationAuthority;
126
127
    /**
128
     * @var Locale
129
     */
130
    private $preferredLocale;
131
132
    /**
133
     * @var boolean
134
     */
135
    private $forgotten;
136
137
    public static function create(
138
        IdentityId $id,
139
        Institution $institution,
140
        NameId $nameId,
141
        CommonName $commonName,
142
        Email $email,
143
        Locale $preferredLocale
144
    ) {
145
        $identity = new self();
146
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
147
148
        return $identity;
149
    }
150
151
    final public function __construct()
152
    {
153
    }
154
155
    public function rename(CommonName $commonName)
156
    {
157
        $this->assertNotForgotten();
158
159
        if ($this->commonName->equals($commonName)) {
160
            return;
161
        }
162
163
        $this->commonName = $commonName;
164
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
165
    }
166
167
    public function changeEmail(Email $email)
168
    {
169
        $this->assertNotForgotten();
170
171
        if ($this->email->equals($email)) {
172
            return;
173
        }
174
175
        $this->email = $email;
176
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
177
    }
178
179
    public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId)
180
    {
181
        $this->assertNotForgotten();
182
        $this->assertUserMayAddSecondFactor();
183
184
        $this->apply(
185
            new YubikeySecondFactorBootstrappedEvent(
186
                $this->id,
187
                $this->nameId,
188
                $this->institution,
189
                $this->commonName,
190
                $this->email,
191
                $this->preferredLocale,
192
                $secondFactorId,
193
                $yubikeyPublicId
194
            )
195
        );
196
    }
197
198 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...
199
        SecondFactorId $secondFactorId,
200
        YubikeyPublicId $yubikeyPublicId,
201
        EmailVerificationWindow $emailVerificationWindow
202
    ) {
203
        $this->assertNotForgotten();
204
        $this->assertUserMayAddSecondFactor();
205
206
        $this->apply(
207
            new YubikeyPossessionProvenEvent(
208
                $this->id,
209
                $this->institution,
210
                $secondFactorId,
211
                $yubikeyPublicId,
212
                $emailVerificationWindow,
213
                TokenGenerator::generateNonce(),
214
                $this->commonName,
215
                $this->email,
216
                $this->preferredLocale
217
            )
218
        );
219
    }
220
221 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...
222
        SecondFactorId $secondFactorId,
223
        PhoneNumber $phoneNumber,
224
        EmailVerificationWindow $emailVerificationWindow
225
    ) {
226
        $this->assertNotForgotten();
227
        $this->assertUserMayAddSecondFactor();
228
229
        $this->apply(
230
            new PhonePossessionProvenEvent(
231
                $this->id,
232
                $this->institution,
233
                $secondFactorId,
234
                $phoneNumber,
235
                $emailVerificationWindow,
236
                TokenGenerator::generateNonce(),
237
                $this->commonName,
238
                $this->email,
239
                $this->preferredLocale
240
            )
241
        );
242
    }
243
244 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...
245
        SecondFactorId $secondFactorId,
246
        StepupProvider $provider,
247
        GssfId $gssfId,
248
        EmailVerificationWindow $emailVerificationWindow
249
    ) {
250
        $this->assertNotForgotten();
251
        $this->assertUserMayAddSecondFactor();
252
253
        $this->apply(
254
            new GssfPossessionProvenEvent(
255
                $this->id,
256
                $this->institution,
257
                $secondFactorId,
258
                $provider,
259
                $gssfId,
260
                $emailVerificationWindow,
261
                TokenGenerator::generateNonce(),
262
                $this->commonName,
263
                $this->email,
264
                $this->preferredLocale
265
            )
266
        );
267
    }
268
269 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...
270
        SecondFactorId $secondFactorId,
271
        U2fKeyHandle $keyHandle,
272
        EmailVerificationWindow $emailVerificationWindow
273
    ) {
274
        $this->assertNotForgotten();
275
        $this->assertUserMayAddSecondFactor();
276
277
        $this->apply(
278
            new U2fDevicePossessionProvenEvent(
279
                $this->id,
280
                $this->institution,
281
                $secondFactorId,
282
                $keyHandle,
283
                $emailVerificationWindow,
284
                TokenGenerator::generateNonce(),
285
                $this->commonName,
286
                $this->email,
287
                $this->preferredLocale
288
            )
289
        );
290
    }
291
292
    public function verifyEmail($verificationNonce)
293
    {
294
        $this->assertNotForgotten();
295
296
        $secondFactorToVerify = null;
297
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
298
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
299
            if ($secondFactor->hasNonce($verificationNonce)) {
300
                $secondFactorToVerify = $secondFactor;
301
            }
302
        }
303
304
        if (!$secondFactorToVerify) {
305
            throw new DomainException(
306
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
307
            );
308
        }
309
310
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
311
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
312
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
313
        }
314
315
        $secondFactorToVerify->verifyEmail();
316
    }
317
318
    public function vetSecondFactor(
319
        IdentityApi $registrant,
320
        SecondFactorId $registrantsSecondFactorId,
321
        SecondFactorType $registrantsSecondFactorType,
322
        SecondFactorIdentifier $registrantsSecondFactorIdentifier,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $registrantsSecondFactorIdentifier exceeds the maximum configured length of 30.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
323
        $registrationCode,
324
        DocumentNumber $documentNumber,
325
        $identityVerified
326
    ) {
327
        $this->assertNotForgotten();
328
329
        if ($this->registrationAuthority === null) {
330
            throw new DomainException('Cannot vet second factor: vetting Identity is not a registration authority');
331
        }
332
333
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
334
        $secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa();
335
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
336
337
        if ($registrantsSecondFactor === null) {
338
            throw new DomainException(
339
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId)
340
            );
341
        }
342
343
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo($registrantsSecondFactor)) {
344
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
345
        }
346
347
        if (!$identityVerified) {
348
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
349
        }
350
351
        if (!$registrant->getInstitution()->equals($this->institution)
352
        && !$this->registrationAuthority->getRole()->isSraa()) {
353
            throw new DomainException(sprintf(
354
                'Cannot vet registrant of institution "%s", because the RA belongs to a different institution: "%s"',
355
                $registrant->getInstitution()->getInstitution(),
356
                $this->institution->getInstitution()
357
            ));
358
        }
359
360
        $registrant->complyWithVettingOfSecondFactor(
361
            $registrantsSecondFactorId,
362
            $registrantsSecondFactorType,
363
            $registrantsSecondFactorIdentifier,
364
            $registrationCode,
365
            $documentNumber
366
        );
367
    }
368
369
    public function complyWithVettingOfSecondFactor(
370
        SecondFactorId $secondFactorId,
371
        SecondFactorType $secondFactorType,
372
        SecondFactorIdentifier $secondFactorIdentifier,
373
        $registrationCode,
374
        DocumentNumber $documentNumber
375
    ) {
376
        $this->assertNotForgotten();
377
378
        $secondFactorToVet = null;
379
        foreach ($this->verifiedSecondFactors as $secondFactor) {
380
            /** @var VerifiedSecondFactor $secondFactor */
381
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
382
                $secondFactorToVet = $secondFactor;
383
            }
384
        }
385
386
        if (!$secondFactorToVet) {
387
            throw new DomainException(
388
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
389
                'and second factor identifier'
390
            );
391
        }
392
393
        if (!$secondFactorToVet->canBeVettedNow()) {
394
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
395
        }
396
397
        $secondFactorToVet->vet($documentNumber);
398
    }
399
400
    public function revokeSecondFactor(SecondFactorId $secondFactorId)
401
    {
402
        $this->assertNotForgotten();
403
404
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
405
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId);
406
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
407
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId);
408
        /** @var VettedSecondFactor|null $vettedSecondFactor */
409
        $vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId);
410
411
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
412
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
413
        }
414
415
        if ($unverifiedSecondFactor) {
416
            $unverifiedSecondFactor->revoke();
417
418
            return;
419
        }
420
421
        if ($verifiedSecondFactor) {
422
            $verifiedSecondFactor->revoke();
423
424
            return;
425
        }
426
427
        $vettedSecondFactor->revoke();
428
    }
429
430
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId)
431
    {
432
        $this->assertNotForgotten();
433
434
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
435
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId);
436
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
437
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId);
438
        /** @var VettedSecondFactor|null $vettedSecondFactor */
439
        $vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId);
440
441
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
442
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
443
        }
444
445
        if ($unverifiedSecondFactor) {
446
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
447
448
            return;
449
        }
450
451
        if ($verifiedSecondFactor) {
452
            $verifiedSecondFactor->complyWithRevocation($authorityId);
453
454
            return;
455
        }
456
457
        $vettedSecondFactor->complyWithRevocation($authorityId);
458
    }
459
460
    /**
461
     * @param Institution               $institution
462
     * @param RegistrationAuthorityRole $role
463
     * @param Location                  $location
464
     * @param ContactInformation        $contactInformation
465
     * @return void
466
     */
467
    public function accreditWith(
468
        RegistrationAuthorityRole $role,
469
        Institution $institution,
470
        Location $location,
471
        ContactInformation $contactInformation
472
    ) {
473
        $this->assertNotForgotten();
474
475
        if (!$this->institution->equals($institution)) {
476
            throw new DomainException('An Identity may only be accredited within its own institution');
477
        }
478
479
        if (!$this->vettedSecondFactors->count()) {
480
            throw new DomainException(
481
                'An Identity must have at least one vetted second factor before it can be accredited'
482
            );
483
        }
484
485
        if ($this->registrationAuthority) {
486
            throw new DomainException('Cannot accredit Identity as it has already been accredited');
487
        }
488
489
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
490
            $this->apply(new IdentityAccreditedAsRaEvent(
491
                $this->id,
492
                $this->nameId,
493
                $this->institution,
494
                $role,
495
                $location,
496
                $contactInformation
497
            ));
498 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...
499
            $this->apply(new IdentityAccreditedAsRaaEvent(
500
                $this->id,
501
                $this->nameId,
502
                $this->institution,
503
                $role,
504
                $location,
505
                $contactInformation
506
            ));
507
        } else {
508
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
509
        }
510
    }
511
512
    public function amendRegistrationAuthorityInformation(Location $location, ContactInformation $contactInformation)
513
    {
514
        $this->assertNotForgotten();
515
516
        if (!$this->registrationAuthority) {
517
            throw new DomainException(
518
                'Cannot amend registration authority information: identity is not a registration authority'
519
            );
520
        }
521
522
        $this->apply(
523
            new RegistrationAuthorityInformationAmendedEvent(
524
                $this->id,
525
                $this->institution,
526
                $this->nameId,
527
                $location,
528
                $contactInformation
529
            )
530
        );
531
    }
532
533
    public function appointAs(RegistrationAuthorityRole $role)
534
    {
535
        $this->assertNotForgotten();
536
537
        if (!$this->registrationAuthority) {
538
            throw new DomainException(
539
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority'
540
            );
541
        }
542
543
        if ($this->registrationAuthority->isAppointedAs($role)) {
544
            return;
545
        }
546
547
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
548
            $this->apply(new AppointedAsRaEvent($this->id, $this->institution, $this->nameId));
549 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...
550
            $this->apply(new AppointedAsRaaEvent($this->id, $this->institution, $this->nameId));
551
        } else {
552
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
553
        }
554
    }
555
556
    public function retractRegistrationAuthority()
557
    {
558
        $this->assertNotForgotten();
559
560
        if (!$this->registrationAuthority) {
561
            throw new DomainException(
562
                'Cannot Retract Registration Authority as the Identity is not a registration authority'
563
            );
564
        }
565
566
        $this->apply(new RegistrationAuthorityRetractedEvent(
567
            $this->id,
568
            $this->institution,
569
            $this->nameId,
570
            $this->commonName,
571
            $this->email
572
        ));
573
    }
574
575
    public function expressPreferredLocale(Locale $preferredLocale)
576
    {
577
        $this->assertNotForgotten();
578
579
        if ($this->preferredLocale === $preferredLocale) {
580
            return;
581
        }
582
583
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
584
    }
585
586
    public function forget()
587
    {
588
        $this->assertNotForgotten();
589
590
        if ($this->registrationAuthority) {
591
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
592
        }
593
594
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
595
    }
596
597
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event)
598
    {
599
        $this->id                      = $event->identityId;
600
        $this->institution             = $event->identityInstitution;
601
        $this->nameId                  = $event->nameId;
602
        $this->commonName              = $event->commonName;
603
        $this->email                   = $event->email;
604
        $this->preferredLocale         = $event->preferredLocale;
605
        $this->forgotten               = false;
606
607
        $this->unverifiedSecondFactors = new SecondFactorCollection();
608
        $this->verifiedSecondFactors   = new SecondFactorCollection();
609
        $this->vettedSecondFactors     = new SecondFactorCollection();
610
    }
611
612
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event)
613
    {
614
        $this->commonName = $event->commonName;
615
    }
616
617
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event)
618
    {
619
        $this->email = $event->email;
620
    }
621
622
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event)
623
    {
624
        $secondFactor = VettedSecondFactor::create(
625
            $event->secondFactorId,
626
            $this,
627
            new SecondFactorType('yubikey'),
628
            $event->yubikeyPublicId
629
        );
630
631
        $this->vettedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
632
    }
633
634 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...
635
    {
636
        $secondFactor = UnverifiedSecondFactor::create(
637
            $event->secondFactorId,
638
            $this,
639
            new SecondFactorType('yubikey'),
640
            $event->yubikeyPublicId,
641
            $event->emailVerificationWindow,
642
            $event->emailVerificationNonce
643
        );
644
645
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
646
    }
647
648 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...
649
    {
650
        $secondFactor = UnverifiedSecondFactor::create(
651
            $event->secondFactorId,
652
            $this,
653
            new SecondFactorType('sms'),
654
            $event->phoneNumber,
655
            $event->emailVerificationWindow,
656
            $event->emailVerificationNonce
657
        );
658
659
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
660
    }
661
662 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...
663
    {
664
        $secondFactor = UnverifiedSecondFactor::create(
665
            $event->secondFactorId,
666
            $this,
667
            new SecondFactorType((string) $event->stepupProvider),
668
            $event->gssfId,
669
            $event->emailVerificationWindow,
670
            $event->emailVerificationNonce
671
        );
672
673
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
674
    }
675
676 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...
677
    {
678
        $secondFactor = UnverifiedSecondFactor::create(
679
            $event->secondFactorId,
680
            $this,
681
            new SecondFactorType('u2f'),
682
            $event->keyHandle,
683
            $event->emailVerificationWindow,
684
            $event->emailVerificationNonce
685
        );
686
687
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
688
    }
689
690
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event)
691
    {
692
        $secondFactorId = (string) $event->secondFactorId;
693
694
        /** @var UnverifiedSecondFactor $unverified */
695
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
696
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
697
698
        $this->unverifiedSecondFactors->remove($secondFactorId);
699
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
700
    }
701
702
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event)
703
    {
704
        $secondFactorId = (string) $event->secondFactorId;
705
706
        /** @var VerifiedSecondFactor $verified */
707
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
708
        $vetted = $verified->asVetted();
709
710
        $this->verifiedSecondFactors->remove($secondFactorId);
711
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
712
    }
713
714
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event)
715
    {
716
        $this->unverifiedSecondFactors->remove((string) $event->secondFactorId);
717
    }
718
719
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
720
        CompliedWithUnverifiedSecondFactorRevocationEvent $event
721
    ) {
722
        $this->unverifiedSecondFactors->remove((string) $event->secondFactorId);
723
    }
724
725
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event)
726
    {
727
        $this->verifiedSecondFactors->remove((string) $event->secondFactorId);
728
    }
729
730
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
731
        CompliedWithVerifiedSecondFactorRevocationEvent $event
732
    ) {
733
        $this->verifiedSecondFactors->remove((string) $event->secondFactorId);
734
    }
735
736
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event)
737
    {
738
        $this->vettedSecondFactors->remove((string) $event->secondFactorId);
739
    }
740
741
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
742
        CompliedWithVettedSecondFactorRevocationEvent $event
743
    ) {
744
        $this->vettedSecondFactors->remove((string) $event->secondFactorId);
745
    }
746
747
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event)
748
    {
749
        $this->registrationAuthority = RegistrationAuthority::accreditWith(
750
            $event->registrationAuthorityRole,
751
            $event->location,
752
            $event->contactInformation
753
        );
754
    }
755
756
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event)
757
    {
758
        $this->registrationAuthority = RegistrationAuthority::accreditWith(
759
            $event->registrationAuthorityRole,
760
            $event->location,
761
            $event->contactInformation
762
        );
763
    }
764
765
    protected function applyRegistrationAuthorityInformationAmendedEvent(
766
        RegistrationAuthorityInformationAmendedEvent $event
767
    ) {
768
        $this->registrationAuthority->amendInformation($event->location, $event->contactInformation);
769
    }
770
771
    protected function applyAppointedAsRaEvent(AppointedAsRaEvent $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...
772
    {
773
        $this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
774
    }
775
776
    protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $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...
777
    {
778
        $this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
779
    }
780
781
    protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $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...
782
    {
783
        $this->registrationAuthority = null;
784
    }
785
786
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event)
787
    {
788
        $this->preferredLocale = $event->preferredLocale;
789
    }
790
791
    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...
792
    {
793
        $this->commonName = CommonName::unknown();
794
        $this->email      = Email::unknown();
795
        $this->forgotten  = true;
796
    }
797
798
    public function getAggregateRootId()
799
    {
800
        return $this->id;
801
    }
802
803
    protected function getChildEntities()
804
    {
805
        return array_merge(
806
            $this->unverifiedSecondFactors->getValues(),
807
            $this->verifiedSecondFactors->getValues(),
808
            $this->vettedSecondFactors->getValues()
809
        );
810
    }
811
812
    /**
813
     * @throws DomainException
814
     */
815
    private function assertNotForgotten()
816
    {
817
        if ($this->forgotten) {
818
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
819
        }
820
    }
821
822
    /**
823
     * @throws DomainException
824
     */
825
    private function assertUserMayAddSecondFactor()
826
    {
827
        if (count($this->unverifiedSecondFactors) +
828
            count($this->verifiedSecondFactors) +
829
            count($this->vettedSecondFactors) > 0
830
        ) {
831
            throw new DomainException('User may not have more than one token');
832
        }
833
    }
834
835
    public function getId()
836
    {
837
        return $this->id;
838
    }
839
840
    /**
841
     * @return NameId
842
     */
843
    public function getNameId()
844
    {
845
        return $this->nameId;
846
    }
847
848
    /**
849
     * @return Institution
850
     */
851
    public function getInstitution()
852
    {
853
        return $this->institution;
854
    }
855
856
    public function getCommonName()
857
    {
858
        return $this->commonName;
859
    }
860
861
    public function getEmail()
862
    {
863
        return $this->email;
864
    }
865
866
    public function getPreferredLocale()
867
    {
868
        return $this->preferredLocale;
869
    }
870
871
    /**
872
     * @param SecondFactorId $secondFactorId
873
     * @return VerifiedSecondFactor|null
874
     */
875
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId)
876
    {
877
        return $this->verifiedSecondFactors->get((string) $secondFactorId);
878
    }
879
}
880