Completed
Push — develop ( 6a0b2d...4b76b9 )
by
unknown
31s queued 28s
created

Identity::assertNotForgotten()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
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\DateTime\DateTime;
23
use Surfnet\Stepup\Exception\DomainException;
24
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
25
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
26
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
27
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
28
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
29
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
30
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
31
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
32
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
33
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
34
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
35
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
36
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenAndVerifiedEvent;
37
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
38
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
39
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
40
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
41
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
42
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
43
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
44
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
45
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenAndVerifiedEvent;
46
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
47
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
48
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
49
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
50
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenAndVerifiedEvent;
51
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
52
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
53
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
54
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
55
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenAndVerifiedEvent;
56
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
57
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
58
use Surfnet\Stepup\Identity\Value\CommonName;
59
use Surfnet\Stepup\Identity\Value\ContactInformation;
60
use Surfnet\Stepup\Identity\Value\DocumentNumber;
61
use Surfnet\Stepup\Identity\Value\Email;
62
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow;
63
use Surfnet\Stepup\Identity\Value\GssfId;
64
use Surfnet\Stepup\Identity\Value\IdentityId;
65
use Surfnet\Stepup\Identity\Value\Institution;
66
use Surfnet\Stepup\Identity\Value\Locale;
67
use Surfnet\Stepup\Identity\Value\Location;
68
use Surfnet\Stepup\Identity\Value\NameId;
69
use Surfnet\Stepup\Identity\Value\PhoneNumber;
70
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole;
71
use Surfnet\Stepup\Identity\Value\SecondFactorId;
72
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
73
use Surfnet\Stepup\Identity\Value\StepupProvider;
74
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
75
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
76
use Surfnet\Stepup\Token\TokenGenerator;
77
use Surfnet\StepupBundle\Security\OtpGenerator;
78
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
79
use Surfnet\StepupBundle\Value\SecondFactorType;
80
81
/**
82
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
83
 * @SuppressWarnings(PHPMD.TooManyMethods)
84
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
85
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
86
 */
87
class Identity extends EventSourcedAggregateRoot implements IdentityApi
88
{
89
    /**
90
     * @var IdentityId
91
     */
92
    private $id;
93
94
    /**
95
     * @var Institution
96
     */
97
    private $institution;
98
99
    /**
100
     * @var NameId
101
     */
102
    private $nameId;
103
104
    /**
105
     * @var \Surfnet\Stepup\Identity\Value\CommonName
106
     */
107
    private $commonName;
108
109
    /**
110
     * @var \Surfnet\Stepup\Identity\Value\Email
111
     */
112
    private $email;
113
114
    /**
115
     * @var SecondFactorCollection|UnverifiedSecondFactor[]
116
     */
117
    private $unverifiedSecondFactors;
118
119
    /**
120
     * @var SecondFactorCollection|VerifiedSecondFactor[]
121
     */
122
    private $verifiedSecondFactors;
123
124
    /**
125
     * @var SecondFactorCollection|VettedSecondFactor[]
126
     */
127
    private $vettedSecondFactors;
128
129
    /**
130
     * @var RegistrationAuthority
131
     */
132
    private $registrationAuthority;
133
134
    /**
135
     * @var Locale
136
     */
137
    private $preferredLocale;
138
139
    /**
140
     * @var boolean
141
     */
142
    private $forgotten;
143
144
    /**
145
     * @var int
146
     */
147
    private $maxNumberOfTokens = 1;
148
149
    public static function create(
150
        IdentityId $id,
151
        Institution $institution,
152
        NameId $nameId,
153
        CommonName $commonName,
154
        Email $email,
155
        Locale $preferredLocale
156
    ) {
157
        $identity = new self();
158
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
159
160
        return $identity;
161
    }
162
163
    final public function __construct()
164
    {
165
    }
166
167
    public function rename(CommonName $commonName)
168
    {
169
        $this->assertNotForgotten();
170
171
        if ($this->commonName->equals($commonName)) {
172
            return;
173
        }
174
175
        $this->commonName = $commonName;
176
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
177
    }
178
179
    public function changeEmail(Email $email)
180
    {
181
        $this->assertNotForgotten();
182
183
        if ($this->email->equals($email)) {
184
            return;
185
        }
186
187
        $this->email = $email;
188
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
189
    }
190
191
    /**
192
     * @param int $numberOfTokens
193
     */
194
    public function setMaxNumberOfTokens($numberOfTokens)
195
    {
196
        $this->maxNumberOfTokens = $numberOfTokens;
197
    }
198
199
    public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId)
200
    {
201
        $this->assertNotForgotten();
202
        $this->assertUserMayAddSecondFactor();
203
204
        $this->apply(
205
            new YubikeySecondFactorBootstrappedEvent(
206
                $this->id,
207
                $this->nameId,
208
                $this->institution,
209
                $this->commonName,
210
                $this->email,
211
                $this->preferredLocale,
212
                $secondFactorId,
213
                $yubikeyPublicId
214
            )
215
        );
216
    }
217
218 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...
219
        SecondFactorId $secondFactorId,
220
        YubikeyPublicId $yubikeyPublicId,
221
        $emailVerificationRequired,
222
        EmailVerificationWindow $emailVerificationWindow
223
    ) {
224
        $this->assertNotForgotten();
225
        $this->assertUserMayAddSecondFactor();
226
227
        if ($emailVerificationRequired) {
228
            $emailVerificationNonce = TokenGenerator::generateNonce();
229
230
            $this->apply(
231
                new YubikeyPossessionProvenEvent(
232
                    $this->id,
233
                    $this->institution,
234
                    $secondFactorId,
235
                    $yubikeyPublicId,
236
                    $emailVerificationRequired,
237
                    $emailVerificationWindow,
238
                    $emailVerificationNonce,
239
                    $this->commonName,
240
                    $this->email,
241
                    $this->preferredLocale
242
                )
243
            );
244
        } else {
245
            $this->apply(
246
                new YubikeyPossessionProvenAndVerifiedEvent(
247
                    $this->id,
248
                    $this->institution,
249
                    $secondFactorId,
250
                    $yubikeyPublicId,
251
                    $this->commonName,
252
                    $this->email,
253
                    DateTime::now(),
254
                    OtpGenerator::generate(8)
255
                )
256
            );
257
        }
258
    }
259
260 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...
261
        SecondFactorId $secondFactorId,
262
        PhoneNumber $phoneNumber,
263
        $emailVerificationRequired,
264
        EmailVerificationWindow $emailVerificationWindow
265
    ) {
266
        $this->assertNotForgotten();
267
        $this->assertUserMayAddSecondFactor();
268
269
        if ($emailVerificationRequired) {
270
            $emailVerificationNonce = TokenGenerator::generateNonce();
271
272
            $this->apply(
273
                new PhonePossessionProvenEvent(
274
                    $this->id,
275
                    $this->institution,
276
                    $secondFactorId,
277
                    $phoneNumber,
278
                    $emailVerificationRequired,
279
                    $emailVerificationWindow,
280
                    $emailVerificationNonce,
281
                    $this->commonName,
282
                    $this->email,
283
                    $this->preferredLocale
284
                )
285
            );
286
        } else {
287
            $this->apply(
288
                new PhonePossessionProvenAndVerifiedEvent(
289
                    $this->id,
290
                    $this->institution,
291
                    $secondFactorId,
292
                    $phoneNumber,
293
                    $this->commonName,
294
                    $this->email,
295
                    DateTime::now(),
296
                    OtpGenerator::generate(8)
297
                )
298
            );
299
        }
300
    }
301
302 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...
303
        SecondFactorId $secondFactorId,
304
        StepupProvider $provider,
305
        GssfId $gssfId,
306
        $emailVerificationRequired,
307
        EmailVerificationWindow $emailVerificationWindow
308
    ) {
309
        $this->assertNotForgotten();
310
        $this->assertUserMayAddSecondFactor();
311
312
        if ($emailVerificationRequired) {
313
            $emailVerificationNonce = TokenGenerator::generateNonce();
314
315
            $this->apply(
316
                new GssfPossessionProvenEvent(
317
                    $this->id,
318
                    $this->institution,
319
                    $secondFactorId,
320
                    $provider,
321
                    $gssfId,
322
                    $emailVerificationRequired,
323
                    $emailVerificationWindow,
324
                    $emailVerificationNonce,
325
                    $this->commonName,
326
                    $this->email,
327
                    $this->preferredLocale
328
                )
329
            );
330
        } else {
331
            $this->apply(
332
                new GssfPossessionProvenAndVerifiedEvent(
333
                    $this->id,
334
                    $this->institution,
335
                    $secondFactorId,
336
                    $provider,
337
                    $gssfId,
338
                    $this->commonName,
339
                    $this->email,
340
                    DateTime::now(),
341
                    OtpGenerator::generate(8)
342
                )
343
            );
344
        }
345
    }
346
347 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...
348
        SecondFactorId $secondFactorId,
349
        U2fKeyHandle $keyHandle,
350
        $emailVerificationRequired,
351
        EmailVerificationWindow $emailVerificationWindow
352
    ) {
353
        $this->assertNotForgotten();
354
        $this->assertUserMayAddSecondFactor();
355
356
        if ($emailVerificationRequired) {
357
            $emailVerificationNonce = TokenGenerator::generateNonce();
358
359
            $this->apply(
360
                new U2fDevicePossessionProvenEvent(
361
                    $this->id,
362
                    $this->institution,
363
                    $secondFactorId,
364
                    $keyHandle,
365
                    $emailVerificationRequired,
366
                    $emailVerificationWindow,
367
                    $emailVerificationNonce,
368
                    $this->commonName,
369
                    $this->email,
370
                    $this->preferredLocale
371
                )
372
            );
373
        } else {
374
            $this->apply(
375
                new U2fDevicePossessionProvenAndVerifiedEvent(
376
                    $this->id,
377
                    $this->institution,
378
                    $secondFactorId,
379
                    $keyHandle,
380
                    $this->commonName,
381
                    $this->email,
382
                    DateTime::now(),
383
                    OtpGenerator::generate(8)
384
                )
385
            );
386
        }
387
    }
388
389
    public function verifyEmail($verificationNonce)
390
    {
391
        $this->assertNotForgotten();
392
393
        $secondFactorToVerify = null;
394
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
395
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
396
            if ($secondFactor->hasNonce($verificationNonce)) {
397
                $secondFactorToVerify = $secondFactor;
398
            }
399
        }
400
401
        if (!$secondFactorToVerify) {
402
            throw new DomainException(
403
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
404
            );
405
        }
406
407
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
408
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
409
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
410
        }
411
412
        $secondFactorToVerify->verifyEmail();
413
    }
414
415
    public function vetSecondFactor(
416
        IdentityApi $registrant,
417
        SecondFactorId $registrantsSecondFactorId,
418
        SecondFactorType $registrantsSecondFactorType,
419
        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...
420
        $registrationCode,
421
        DocumentNumber $documentNumber,
422
        $identityVerified,
423
        SecondFactorTypeService $secondFactorTypeService
424
    ) {
425
        $this->assertNotForgotten();
426
427
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
428
        $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...
429
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
430
431
        if ($registrantsSecondFactor === null) {
432
            throw new DomainException(
433
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId)
434
            );
435
        }
436
437
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo(
438
            $registrantsSecondFactor,
439
            $secondFactorTypeService
440
        )) {
441
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
442
        }
443
444
        if (!$identityVerified) {
445
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
446
        }
447
448
        $registrant->complyWithVettingOfSecondFactor(
449
            $registrantsSecondFactorId,
450
            $registrantsSecondFactorType,
451
            $registrantsSecondFactorIdentifier,
452
            $registrationCode,
453
            $documentNumber
454
        );
455
    }
456
457
    public function complyWithVettingOfSecondFactor(
458
        SecondFactorId $secondFactorId,
459
        SecondFactorType $secondFactorType,
460
        SecondFactorIdentifier $secondFactorIdentifier,
461
        $registrationCode,
462
        DocumentNumber $documentNumber
463
    ) {
464
        $this->assertNotForgotten();
465
466
        $secondFactorToVet = null;
467
        foreach ($this->verifiedSecondFactors as $secondFactor) {
468
            /** @var VerifiedSecondFactor $secondFactor */
469
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
470
                $secondFactorToVet = $secondFactor;
471
            }
472
        }
473
474
        if (!$secondFactorToVet) {
475
            throw new DomainException(
476
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
477
                'and second factor identifier'
478
            );
479
        }
480
481
        if (!$secondFactorToVet->canBeVettedNow()) {
482
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
483
        }
484
485
        $secondFactorToVet->vet($documentNumber);
486
    }
487
488
    public function revokeSecondFactor(SecondFactorId $secondFactorId)
489
    {
490
        $this->assertNotForgotten();
491
492
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
493
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId);
494
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
495
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId);
496
        /** @var VettedSecondFactor|null $vettedSecondFactor */
497
        $vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId);
498
499
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
500
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
501
        }
502
503
        if ($unverifiedSecondFactor) {
504
            $unverifiedSecondFactor->revoke();
505
506
            return;
507
        }
508
509
        if ($verifiedSecondFactor) {
510
            $verifiedSecondFactor->revoke();
511
512
            return;
513
        }
514
515
        $vettedSecondFactor->revoke();
516
    }
517
518
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId)
519
    {
520
        $this->assertNotForgotten();
521
522
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
523
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId);
524
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
525
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId);
526
        /** @var VettedSecondFactor|null $vettedSecondFactor */
527
        $vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId);
528
529
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
530
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
531
        }
532
533
        if ($unverifiedSecondFactor) {
534
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
535
536
            return;
537
        }
538
539
        if ($verifiedSecondFactor) {
540
            $verifiedSecondFactor->complyWithRevocation($authorityId);
541
542
            return;
543
        }
544
545
        $vettedSecondFactor->complyWithRevocation($authorityId);
546
    }
547
548
    /**
549
     * @param Institution               $institution
550
     * @param RegistrationAuthorityRole $role
551
     * @param Location                  $location
552
     * @param ContactInformation        $contactInformation
553
     * @return void
554
     */
555
    public function accreditWith(
556
        RegistrationAuthorityRole $role,
557
        Institution $institution,
558
        Location $location,
559
        ContactInformation $contactInformation
560
    ) {
561
        $this->assertNotForgotten();
562
563
        if (!$this->institution->equals($institution)) {
564
            throw new DomainException('An Identity may only be accredited within its own institution');
565
        }
566
567
        if (!$this->vettedSecondFactors->count()) {
568
            throw new DomainException(
569
                'An Identity must have at least one vetted second factor before it can be accredited'
570
            );
571
        }
572
573
        if ($this->registrationAuthority) {
574
            throw new DomainException('Cannot accredit Identity as it has already been accredited');
575
        }
576
577
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
578
            $this->apply(new IdentityAccreditedAsRaEvent(
579
                $this->id,
580
                $this->nameId,
581
                $this->institution,
582
                $role,
583
                $location,
584
                $contactInformation
585
            ));
586 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...
587
            $this->apply(new IdentityAccreditedAsRaaEvent(
588
                $this->id,
589
                $this->nameId,
590
                $this->institution,
591
                $role,
592
                $location,
593
                $contactInformation
594
            ));
595
        } else {
596
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
597
        }
598
    }
599
600
    public function amendRegistrationAuthorityInformation(Location $location, ContactInformation $contactInformation)
601
    {
602
        $this->assertNotForgotten();
603
604
        if (!$this->registrationAuthority) {
605
            throw new DomainException(
606
                'Cannot amend registration authority information: identity is not a registration authority'
607
            );
608
        }
609
610
        $this->apply(
611
            new RegistrationAuthorityInformationAmendedEvent(
612
                $this->id,
613
                $this->institution,
614
                $this->nameId,
615
                $location,
616
                $contactInformation
617
            )
618
        );
619
    }
620
621
    public function appointAs(RegistrationAuthorityRole $role)
622
    {
623
        $this->assertNotForgotten();
624
625
        if (!$this->registrationAuthority) {
626
            throw new DomainException(
627
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority'
628
            );
629
        }
630
631
        if ($this->registrationAuthority->isAppointedAs($role)) {
632
            return;
633
        }
634
635
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
636
            $this->apply(new AppointedAsRaEvent($this->id, $this->institution, $this->nameId));
637 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...
638
            $this->apply(new AppointedAsRaaEvent($this->id, $this->institution, $this->nameId));
639
        } else {
640
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
641
        }
642
    }
643
644
    public function retractRegistrationAuthority()
645
    {
646
        $this->assertNotForgotten();
647
648
        if (!$this->registrationAuthority) {
649
            throw new DomainException(
650
                'Cannot Retract Registration Authority as the Identity is not a registration authority'
651
            );
652
        }
653
654
        $this->apply(new RegistrationAuthorityRetractedEvent(
655
            $this->id,
656
            $this->institution,
657
            $this->nameId,
658
            $this->commonName,
659
            $this->email
660
        ));
661
    }
662
663
    public function expressPreferredLocale(Locale $preferredLocale)
664
    {
665
        $this->assertNotForgotten();
666
667
        if ($this->preferredLocale === $preferredLocale) {
668
            return;
669
        }
670
671
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
672
    }
673
674
    public function forget()
675
    {
676
        $this->assertNotForgotten();
677
678
        if ($this->registrationAuthority) {
679
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
680
        }
681
682
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
683
    }
684
685
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event)
686
    {
687
        $this->id                      = $event->identityId;
688
        $this->institution             = $event->identityInstitution;
689
        $this->nameId                  = $event->nameId;
690
        $this->commonName              = $event->commonName;
691
        $this->email                   = $event->email;
692
        $this->preferredLocale         = $event->preferredLocale;
693
        $this->forgotten               = false;
694
695
        $this->unverifiedSecondFactors = new SecondFactorCollection();
696
        $this->verifiedSecondFactors   = new SecondFactorCollection();
697
        $this->vettedSecondFactors     = new SecondFactorCollection();
698
    }
699
700
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event)
701
    {
702
        $this->commonName = $event->commonName;
703
    }
704
705
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event)
706
    {
707
        $this->email = $event->email;
708
    }
709
710
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event)
711
    {
712
        $secondFactor = VettedSecondFactor::create(
713
            $event->secondFactorId,
714
            $this,
715
            new SecondFactorType('yubikey'),
716
            $event->yubikeyPublicId
717
        );
718
719
        $this->vettedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
720
    }
721
722 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...
723
    {
724
        $secondFactor = UnverifiedSecondFactor::create(
725
            $event->secondFactorId,
726
            $this,
727
            new SecondFactorType('yubikey'),
728
            $event->yubikeyPublicId,
729
            $event->emailVerificationWindow,
730
            $event->emailVerificationNonce
731
        );
732
733
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
734
    }
735
736 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...
737
    {
738
        $secondFactor = VerifiedSecondFactor::create(
739
            $event->secondFactorId,
740
            $this,
741
            new SecondFactorType('yubikey'),
742
            $event->yubikeyPublicId,
743
            $event->registrationRequestedAt,
744
            $event->registrationCode
745
        );
746
747
        $this->verifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
748
    }
749
750 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...
751
    {
752
        $secondFactor = UnverifiedSecondFactor::create(
753
            $event->secondFactorId,
754
            $this,
755
            new SecondFactorType('sms'),
756
            $event->phoneNumber,
757
            $event->emailVerificationWindow,
758
            $event->emailVerificationNonce
759
        );
760
761
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
762
    }
763
764 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...
765
    {
766
        $secondFactor = VerifiedSecondFactor::create(
767
            $event->secondFactorId,
768
            $this,
769
            new SecondFactorType('sms'),
770
            $event->phoneNumber,
771
            $event->registrationRequestedAt,
772
            $event->registrationCode
773
        );
774
775
        $this->verifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
776
    }
777
778 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...
779
    {
780
        $secondFactor = UnverifiedSecondFactor::create(
781
            $event->secondFactorId,
782
            $this,
783
            new SecondFactorType((string) $event->stepupProvider),
784
            $event->gssfId,
785
            $event->emailVerificationWindow,
786
            $event->emailVerificationNonce
787
        );
788
789
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
790
    }
791
792 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...
793
    {
794
        $secondFactor = VerifiedSecondFactor::create(
795
            $event->secondFactorId,
796
            $this,
797
            new SecondFactorType((string) $event->stepupProvider),
798
            $event->gssfId,
799
            $event->registrationRequestedAt,
800
            $event->registrationCode
801
        );
802
803
        $this->verifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
804
    }
805
806 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...
807
    {
808
        $secondFactor = UnverifiedSecondFactor::create(
809
            $event->secondFactorId,
810
            $this,
811
            new SecondFactorType('u2f'),
812
            $event->keyHandle,
813
            $event->emailVerificationWindow,
814
            $event->emailVerificationNonce
815
        );
816
817
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
818
    }
819
820 View Code Duplication
    protected function applyU2fDevicePossessionProvenAndVerifiedEvent(U2fDevicePossessionProvenAndVerifiedEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
821
    {
822
        $secondFactor = VerifiedSecondFactor::create(
823
            $event->secondFactorId,
824
            $this,
825
            new SecondFactorType('u2f'),
826
            $event->keyHandle,
827
            $event->registrationRequestedAt,
828
            $event->registrationCode
829
        );
830
831
        $this->verifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
832
    }
833
834
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event)
835
    {
836
        $secondFactorId = (string) $event->secondFactorId;
837
838
        /** @var UnverifiedSecondFactor $unverified */
839
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
840
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
841
842
        $this->unverifiedSecondFactors->remove($secondFactorId);
843
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
844
    }
845
846
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event)
847
    {
848
        $secondFactorId = (string) $event->secondFactorId;
849
850
        /** @var VerifiedSecondFactor $verified */
851
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
852
        $vetted = $verified->asVetted();
853
854
        $this->verifiedSecondFactors->remove($secondFactorId);
855
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
856
    }
857
858
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event)
859
    {
860
        $this->unverifiedSecondFactors->remove((string) $event->secondFactorId);
861
    }
862
863
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
864
        CompliedWithUnverifiedSecondFactorRevocationEvent $event
865
    ) {
866
        $this->unverifiedSecondFactors->remove((string) $event->secondFactorId);
867
    }
868
869
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event)
870
    {
871
        $this->verifiedSecondFactors->remove((string) $event->secondFactorId);
872
    }
873
874
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
875
        CompliedWithVerifiedSecondFactorRevocationEvent $event
876
    ) {
877
        $this->verifiedSecondFactors->remove((string) $event->secondFactorId);
878
    }
879
880
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event)
881
    {
882
        $this->vettedSecondFactors->remove((string) $event->secondFactorId);
883
    }
884
885
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
886
        CompliedWithVettedSecondFactorRevocationEvent $event
887
    ) {
888
        $this->vettedSecondFactors->remove((string) $event->secondFactorId);
889
    }
890
891
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event)
892
    {
893
        $this->registrationAuthority = RegistrationAuthority::accreditWith(
894
            $event->registrationAuthorityRole,
895
            $event->location,
896
            $event->contactInformation
897
        );
898
    }
899
900
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event)
901
    {
902
        $this->registrationAuthority = RegistrationAuthority::accreditWith(
903
            $event->registrationAuthorityRole,
904
            $event->location,
905
            $event->contactInformation
906
        );
907
    }
908
909
    protected function applyRegistrationAuthorityInformationAmendedEvent(
910
        RegistrationAuthorityInformationAmendedEvent $event
911
    ) {
912
        $this->registrationAuthority->amendInformation($event->location, $event->contactInformation);
913
    }
914
915
    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...
916
    {
917
        $this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
918
    }
919
920
    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...
921
    {
922
        $this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
923
    }
924
925
    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...
926
    {
927
        $this->registrationAuthority = null;
928
    }
929
930
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event)
931
    {
932
        $this->preferredLocale = $event->preferredLocale;
933
    }
934
935
    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...
936
    {
937
        $this->commonName = CommonName::unknown();
938
        $this->email      = Email::unknown();
939
        $this->forgotten  = true;
940
    }
941
942
    public function getAggregateRootId()
943
    {
944
        return $this->id;
945
    }
946
947
    protected function getChildEntities()
948
    {
949
        return array_merge(
950
            $this->unverifiedSecondFactors->getValues(),
951
            $this->verifiedSecondFactors->getValues(),
952
            $this->vettedSecondFactors->getValues()
953
        );
954
    }
955
956
    /**
957
     * @throws DomainException
958
     */
959
    private function assertNotForgotten()
960
    {
961
        if ($this->forgotten) {
962
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
963
        }
964
    }
965
966
    /**
967
     * @throws DomainException
968
     */
969
    private function assertUserMayAddSecondFactor()
970
    {
971
        if (count($this->unverifiedSecondFactors) +
972
            count($this->verifiedSecondFactors) +
973
            count($this->vettedSecondFactors) >= $this->maxNumberOfTokens
974
        ) {
975
            throw new DomainException(
976
                sprintf('User may not have more than %d token(s)', $this->maxNumberOfTokens)
977
            );
978
        }
979
    }
980
981
    public function getId()
982
    {
983
        return $this->id;
984
    }
985
986
    /**
987
     * @return NameId
988
     */
989
    public function getNameId()
990
    {
991
        return $this->nameId;
992
    }
993
994
    /**
995
     * @return Institution
996
     */
997
    public function getInstitution()
998
    {
999
        return $this->institution;
1000
    }
1001
1002
    public function getCommonName()
1003
    {
1004
        return $this->commonName;
1005
    }
1006
1007
    public function getEmail()
1008
    {
1009
        return $this->email;
1010
    }
1011
1012
    public function getPreferredLocale()
1013
    {
1014
        return $this->preferredLocale;
1015
    }
1016
1017
    /**
1018
     * @param SecondFactorId $secondFactorId
1019
     * @return VerifiedSecondFactor|null
1020
     */
1021
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId)
1022
    {
1023
        return $this->verifiedSecondFactors->get((string) $secondFactorId);
1024
    }
1025
}
1026