Completed
Push — feature/multiple-second-factor... ( 32a175 )
by
unknown
18:02
created

Identity   F

Complexity

Total Complexity 94

Size/Duplication

Total Lines 806
Duplicated Lines 19.23 %

Coupling/Cohesion

Components 1
Dependencies 38

Importance

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

56 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 setMaxNumberOfTokens() 0 4 1
A bootstrapYubikeySecondFactor() 0 18 1
A provePossessionOfYubikey() 22 22 1
A provePossessionOfPhone() 22 22 1
B provePossessionOfGssf() 24 24 1
A provePossessionOfU2fDevice() 22 22 1
B verifyEmail() 0 25 5
B vetSecondFactor() 0 41 4
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 11 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\Service\SecondFactorTypeService;
73
use Surfnet\StepupBundle\Value\SecondFactorType;
74
75
/**
76
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
77
 * @SuppressWarnings(PHPMD.TooManyMethods)
78
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
79
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
80
 */
81
class Identity extends EventSourcedAggregateRoot implements IdentityApi
82
{
83
    /**
84
     * @var IdentityId
85
     */
86
    private $id;
87
88
    /**
89
     * @var Institution
90
     */
91
    private $institution;
92
93
    /**
94
     * @var NameId
95
     */
96
    private $nameId;
97
98
    /**
99
     * @var \Surfnet\Stepup\Identity\Value\CommonName
100
     */
101
    private $commonName;
102
103
    /**
104
     * @var \Surfnet\Stepup\Identity\Value\Email
105
     */
106
    private $email;
107
108
    /**
109
     * @var SecondFactorCollection|UnverifiedSecondFactor[]
110
     */
111
    private $unverifiedSecondFactors;
112
113
    /**
114
     * @var SecondFactorCollection|VerifiedSecondFactor[]
115
     */
116
    private $verifiedSecondFactors;
117
118
    /**
119
     * @var SecondFactorCollection|VettedSecondFactor[]
120
     */
121
    private $vettedSecondFactors;
122
123
    /**
124
     * @var RegistrationAuthority
125
     */
126
    private $registrationAuthority;
127
128
    /**
129
     * @var Locale
130
     */
131
    private $preferredLocale;
132
133
    /**
134
     * @var boolean
135
     */
136
    private $forgotten;
137
138
    /**
139
     * @var int
140
     */
141
    private $maxNumberOfTokens = 1;
142
143
    public static function create(
144
        IdentityId $id,
145
        Institution $institution,
146
        NameId $nameId,
147
        CommonName $commonName,
148
        Email $email,
149
        Locale $preferredLocale
150
    ) {
151
        $identity = new self();
152
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
153
154
        return $identity;
155
    }
156
157
    final public function __construct()
158
    {
159
    }
160
161
    public function rename(CommonName $commonName)
162
    {
163
        $this->assertNotForgotten();
164
165
        if ($this->commonName->equals($commonName)) {
166
            return;
167
        }
168
169
        $this->commonName = $commonName;
170
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
171
    }
172
173
    public function changeEmail(Email $email)
174
    {
175
        $this->assertNotForgotten();
176
177
        if ($this->email->equals($email)) {
178
            return;
179
        }
180
181
        $this->email = $email;
182
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
183
    }
184
185
    /**
186
     * @param int $numberOfTokens
187
     */
188
    public function setMaxNumberOfTokens($numberOfTokens)
189
    {
190
        $this->maxNumberOfTokens = $numberOfTokens;
191
    }
192
193
    public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId)
194
    {
195
        $this->assertNotForgotten();
196
        $this->assertUserMayAddSecondFactor();
197
198
        $this->apply(
199
            new YubikeySecondFactorBootstrappedEvent(
200
                $this->id,
201
                $this->nameId,
202
                $this->institution,
203
                $this->commonName,
204
                $this->email,
205
                $this->preferredLocale,
206
                $secondFactorId,
207
                $yubikeyPublicId
208
            )
209
        );
210
    }
211
212 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...
213
        SecondFactorId $secondFactorId,
214
        YubikeyPublicId $yubikeyPublicId,
215
        EmailVerificationWindow $emailVerificationWindow
216
    ) {
217
        $this->assertNotForgotten();
218
        $this->assertUserMayAddSecondFactor();
219
220
        $this->apply(
221
            new YubikeyPossessionProvenEvent(
222
                $this->id,
223
                $this->institution,
224
                $secondFactorId,
225
                $yubikeyPublicId,
226
                $emailVerificationWindow,
227
                TokenGenerator::generateNonce(),
228
                $this->commonName,
229
                $this->email,
230
                $this->preferredLocale
231
            )
232
        );
233
    }
234
235 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...
236
        SecondFactorId $secondFactorId,
237
        PhoneNumber $phoneNumber,
238
        EmailVerificationWindow $emailVerificationWindow
239
    ) {
240
        $this->assertNotForgotten();
241
        $this->assertUserMayAddSecondFactor();
242
243
        $this->apply(
244
            new PhonePossessionProvenEvent(
245
                $this->id,
246
                $this->institution,
247
                $secondFactorId,
248
                $phoneNumber,
249
                $emailVerificationWindow,
250
                TokenGenerator::generateNonce(),
251
                $this->commonName,
252
                $this->email,
253
                $this->preferredLocale
254
            )
255
        );
256
    }
257
258 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...
259
        SecondFactorId $secondFactorId,
260
        StepupProvider $provider,
261
        GssfId $gssfId,
262
        EmailVerificationWindow $emailVerificationWindow
263
    ) {
264
        $this->assertNotForgotten();
265
        $this->assertUserMayAddSecondFactor();
266
267
        $this->apply(
268
            new GssfPossessionProvenEvent(
269
                $this->id,
270
                $this->institution,
271
                $secondFactorId,
272
                $provider,
273
                $gssfId,
274
                $emailVerificationWindow,
275
                TokenGenerator::generateNonce(),
276
                $this->commonName,
277
                $this->email,
278
                $this->preferredLocale
279
            )
280
        );
281
    }
282
283 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...
284
        SecondFactorId $secondFactorId,
285
        U2fKeyHandle $keyHandle,
286
        EmailVerificationWindow $emailVerificationWindow
287
    ) {
288
        $this->assertNotForgotten();
289
        $this->assertUserMayAddSecondFactor();
290
291
        $this->apply(
292
            new U2fDevicePossessionProvenEvent(
293
                $this->id,
294
                $this->institution,
295
                $secondFactorId,
296
                $keyHandle,
297
                $emailVerificationWindow,
298
                TokenGenerator::generateNonce(),
299
                $this->commonName,
300
                $this->email,
301
                $this->preferredLocale
302
            )
303
        );
304
    }
305
306
    public function verifyEmail($verificationNonce)
307
    {
308
        $this->assertNotForgotten();
309
310
        $secondFactorToVerify = null;
311
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
312
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
313
            if ($secondFactor->hasNonce($verificationNonce)) {
314
                $secondFactorToVerify = $secondFactor;
315
            }
316
        }
317
318
        if (!$secondFactorToVerify) {
319
            throw new DomainException(
320
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
321
            );
322
        }
323
324
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
325
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
326
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
327
        }
328
329
        $secondFactorToVerify->verifyEmail();
330
    }
331
332
    public function vetSecondFactor(
333
        IdentityApi $registrant,
334
        SecondFactorId $registrantsSecondFactorId,
335
        SecondFactorType $registrantsSecondFactorType,
336
        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...
337
        $registrationCode,
338
        DocumentNumber $documentNumber,
339
        $identityVerified,
340
        SecondFactorTypeService $secondFactorTypeService
341
    ) {
342
        $this->assertNotForgotten();
343
344
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
345
        $secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa($secondFactorTypeService);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

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