Completed
Push — master ( 64908a...328605 )
by
unknown
03:10
created

Identity::provePossessionOfPhone()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 30
Code Lines 22

Duplication

Lines 30
Ratio 100 %

Importance

Changes 0
Metric Value
dl 30
loc 30
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 22
nc 2
nop 4
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
        $emailVerificationRequired,
216
        EmailVerificationWindow $emailVerificationWindow
217
    ) {
218
        $this->assertNotForgotten();
219
        $this->assertUserMayAddSecondFactor();
220
221
        $emailVerificationNonce = TokenGenerator::generateNonce();
222
223
        $this->apply(
224
            new YubikeyPossessionProvenEvent(
225
                $this->id,
226
                $this->institution,
227
                $secondFactorId,
228
                $yubikeyPublicId,
229
                $emailVerificationRequired,
230
                $emailVerificationWindow,
231
                $emailVerificationNonce,
232
                $this->commonName,
233
                $this->email,
234
                $this->preferredLocale
235
            )
236
        );
237
238
        if ($emailVerificationRequired === false) {
239
            $this->verifyEmail($emailVerificationNonce);
240
        }
241
    }
242
243 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...
244
        SecondFactorId $secondFactorId,
245
        PhoneNumber $phoneNumber,
246
        $emailVerificationRequired,
247
        EmailVerificationWindow $emailVerificationWindow
248
    ) {
249
        $this->assertNotForgotten();
250
        $this->assertUserMayAddSecondFactor();
251
252
        $emailVerificationNonce = TokenGenerator::generateNonce();
253
254
        $this->apply(
255
            new PhonePossessionProvenEvent(
256
                $this->id,
257
                $this->institution,
258
                $secondFactorId,
259
                $phoneNumber,
260
                $emailVerificationRequired,
261
                $emailVerificationWindow,
262
                $emailVerificationNonce,
263
                $this->commonName,
264
                $this->email,
265
                $this->preferredLocale
266
            )
267
        );
268
269
        if ($emailVerificationRequired === false) {
270
            $this->verifyEmail($emailVerificationNonce);
271
        }
272
    }
273
274 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...
275
        SecondFactorId $secondFactorId,
276
        StepupProvider $provider,
277
        GssfId $gssfId,
278
        $emailVerificationRequired,
279
        EmailVerificationWindow $emailVerificationWindow
280
    ) {
281
        $this->assertNotForgotten();
282
        $this->assertUserMayAddSecondFactor();
283
284
        $emailVerificationNonce = TokenGenerator::generateNonce();
285
286
        $this->apply(
287
            new GssfPossessionProvenEvent(
288
                $this->id,
289
                $this->institution,
290
                $secondFactorId,
291
                $provider,
292
                $gssfId,
293
                $emailVerificationRequired,
294
                $emailVerificationWindow,
295
                $emailVerificationNonce,
296
                $this->commonName,
297
                $this->email,
298
                $this->preferredLocale
299
            )
300
        );
301
302
        if ($emailVerificationRequired === false) {
303
            $this->verifyEmail($emailVerificationNonce);
304
        }
305
    }
306
307 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...
308
        SecondFactorId $secondFactorId,
309
        U2fKeyHandle $keyHandle,
310
        $emailVerificationRequired,
311
        EmailVerificationWindow $emailVerificationWindow
312
    ) {
313
        $this->assertNotForgotten();
314
        $this->assertUserMayAddSecondFactor();
315
316
        $emailVerificationNonce = TokenGenerator::generateNonce();
317
318
        $this->apply(
319
            new U2fDevicePossessionProvenEvent(
320
                $this->id,
321
                $this->institution,
322
                $secondFactorId,
323
                $keyHandle,
324
                $emailVerificationRequired,
325
                $emailVerificationWindow,
326
                $emailVerificationNonce,
327
                $this->commonName,
328
                $this->email,
329
                $this->preferredLocale
330
            )
331
        );
332
333
        if ($emailVerificationRequired === false) {
334
            $this->verifyEmail($emailVerificationNonce);
335
        }
336
    }
337
338
    public function verifyEmail($verificationNonce)
339
    {
340
        $this->assertNotForgotten();
341
342
        $secondFactorToVerify = null;
343
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
344
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
345
            if ($secondFactor->hasNonce($verificationNonce)) {
346
                $secondFactorToVerify = $secondFactor;
347
            }
348
        }
349
350
        if (!$secondFactorToVerify) {
351
            throw new DomainException(
352
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
353
            );
354
        }
355
356
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
357
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
358
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
359
        }
360
361
        $secondFactorToVerify->verifyEmail();
362
    }
363
364
    public function vetSecondFactor(
365
        IdentityApi $registrant,
366
        SecondFactorId $registrantsSecondFactorId,
367
        SecondFactorType $registrantsSecondFactorType,
368
        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...
369
        $registrationCode,
370
        DocumentNumber $documentNumber,
371
        $identityVerified,
372
        SecondFactorTypeService $secondFactorTypeService
373
    ) {
374
        $this->assertNotForgotten();
375
376
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
377
        $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...
378
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
379
380
        if ($registrantsSecondFactor === null) {
381
            throw new DomainException(
382
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId)
383
            );
384
        }
385
386
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo(
387
            $registrantsSecondFactor,
388
            $secondFactorTypeService
389
        )) {
390
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
391
        }
392
393
        if (!$identityVerified) {
394
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
395
        }
396
397
        $registrant->complyWithVettingOfSecondFactor(
398
            $registrantsSecondFactorId,
399
            $registrantsSecondFactorType,
400
            $registrantsSecondFactorIdentifier,
401
            $registrationCode,
402
            $documentNumber
403
        );
404
    }
405
406
    public function complyWithVettingOfSecondFactor(
407
        SecondFactorId $secondFactorId,
408
        SecondFactorType $secondFactorType,
409
        SecondFactorIdentifier $secondFactorIdentifier,
410
        $registrationCode,
411
        DocumentNumber $documentNumber
412
    ) {
413
        $this->assertNotForgotten();
414
415
        $secondFactorToVet = null;
416
        foreach ($this->verifiedSecondFactors as $secondFactor) {
417
            /** @var VerifiedSecondFactor $secondFactor */
418
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
419
                $secondFactorToVet = $secondFactor;
420
            }
421
        }
422
423
        if (!$secondFactorToVet) {
424
            throw new DomainException(
425
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
426
                'and second factor identifier'
427
            );
428
        }
429
430
        if (!$secondFactorToVet->canBeVettedNow()) {
431
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
432
        }
433
434
        $secondFactorToVet->vet($documentNumber);
435
    }
436
437
    public function revokeSecondFactor(SecondFactorId $secondFactorId)
438
    {
439
        $this->assertNotForgotten();
440
441
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
442
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId);
443
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
444
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId);
445
        /** @var VettedSecondFactor|null $vettedSecondFactor */
446
        $vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId);
447
448
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
449
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
450
        }
451
452
        if ($unverifiedSecondFactor) {
453
            $unverifiedSecondFactor->revoke();
454
455
            return;
456
        }
457
458
        if ($verifiedSecondFactor) {
459
            $verifiedSecondFactor->revoke();
460
461
            return;
462
        }
463
464
        $vettedSecondFactor->revoke();
465
    }
466
467
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId)
468
    {
469
        $this->assertNotForgotten();
470
471
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
472
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string) $secondFactorId);
473
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
474
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string) $secondFactorId);
475
        /** @var VettedSecondFactor|null $vettedSecondFactor */
476
        $vettedSecondFactor = $this->vettedSecondFactors->get((string) $secondFactorId);
477
478
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
479
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
480
        }
481
482
        if ($unverifiedSecondFactor) {
483
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
484
485
            return;
486
        }
487
488
        if ($verifiedSecondFactor) {
489
            $verifiedSecondFactor->complyWithRevocation($authorityId);
490
491
            return;
492
        }
493
494
        $vettedSecondFactor->complyWithRevocation($authorityId);
495
    }
496
497
    /**
498
     * @param Institution               $institution
499
     * @param RegistrationAuthorityRole $role
500
     * @param Location                  $location
501
     * @param ContactInformation        $contactInformation
502
     * @return void
503
     */
504
    public function accreditWith(
505
        RegistrationAuthorityRole $role,
506
        Institution $institution,
507
        Location $location,
508
        ContactInformation $contactInformation
509
    ) {
510
        $this->assertNotForgotten();
511
512
        if (!$this->institution->equals($institution)) {
513
            throw new DomainException('An Identity may only be accredited within its own institution');
514
        }
515
516
        if (!$this->vettedSecondFactors->count()) {
517
            throw new DomainException(
518
                'An Identity must have at least one vetted second factor before it can be accredited'
519
            );
520
        }
521
522
        if ($this->registrationAuthority) {
523
            throw new DomainException('Cannot accredit Identity as it has already been accredited');
524
        }
525
526
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
527
            $this->apply(new IdentityAccreditedAsRaEvent(
528
                $this->id,
529
                $this->nameId,
530
                $this->institution,
531
                $role,
532
                $location,
533
                $contactInformation
534
            ));
535 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...
536
            $this->apply(new IdentityAccreditedAsRaaEvent(
537
                $this->id,
538
                $this->nameId,
539
                $this->institution,
540
                $role,
541
                $location,
542
                $contactInformation
543
            ));
544
        } else {
545
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
546
        }
547
    }
548
549
    public function amendRegistrationAuthorityInformation(Location $location, ContactInformation $contactInformation)
550
    {
551
        $this->assertNotForgotten();
552
553
        if (!$this->registrationAuthority) {
554
            throw new DomainException(
555
                'Cannot amend registration authority information: identity is not a registration authority'
556
            );
557
        }
558
559
        $this->apply(
560
            new RegistrationAuthorityInformationAmendedEvent(
561
                $this->id,
562
                $this->institution,
563
                $this->nameId,
564
                $location,
565
                $contactInformation
566
            )
567
        );
568
    }
569
570
    public function appointAs(RegistrationAuthorityRole $role)
571
    {
572
        $this->assertNotForgotten();
573
574
        if (!$this->registrationAuthority) {
575
            throw new DomainException(
576
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority'
577
            );
578
        }
579
580
        if ($this->registrationAuthority->isAppointedAs($role)) {
581
            return;
582
        }
583
584
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
585
            $this->apply(new AppointedAsRaEvent($this->id, $this->institution, $this->nameId));
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 AppointedAsRaaEvent($this->id, $this->institution, $this->nameId));
588
        } else {
589
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
590
        }
591
    }
592
593
    public function retractRegistrationAuthority()
594
    {
595
        $this->assertNotForgotten();
596
597
        if (!$this->registrationAuthority) {
598
            throw new DomainException(
599
                'Cannot Retract Registration Authority as the Identity is not a registration authority'
600
            );
601
        }
602
603
        $this->apply(new RegistrationAuthorityRetractedEvent(
604
            $this->id,
605
            $this->institution,
606
            $this->nameId,
607
            $this->commonName,
608
            $this->email
609
        ));
610
    }
611
612
    public function expressPreferredLocale(Locale $preferredLocale)
613
    {
614
        $this->assertNotForgotten();
615
616
        if ($this->preferredLocale === $preferredLocale) {
617
            return;
618
        }
619
620
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
621
    }
622
623
    public function forget()
624
    {
625
        $this->assertNotForgotten();
626
627
        if ($this->registrationAuthority) {
628
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
629
        }
630
631
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
632
    }
633
634
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event)
635
    {
636
        $this->id                      = $event->identityId;
637
        $this->institution             = $event->identityInstitution;
638
        $this->nameId                  = $event->nameId;
639
        $this->commonName              = $event->commonName;
640
        $this->email                   = $event->email;
641
        $this->preferredLocale         = $event->preferredLocale;
642
        $this->forgotten               = false;
643
644
        $this->unverifiedSecondFactors = new SecondFactorCollection();
645
        $this->verifiedSecondFactors   = new SecondFactorCollection();
646
        $this->vettedSecondFactors     = new SecondFactorCollection();
647
    }
648
649
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event)
650
    {
651
        $this->commonName = $event->commonName;
652
    }
653
654
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event)
655
    {
656
        $this->email = $event->email;
657
    }
658
659
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event)
660
    {
661
        $secondFactor = VettedSecondFactor::create(
662
            $event->secondFactorId,
663
            $this,
664
            new SecondFactorType('yubikey'),
665
            $event->yubikeyPublicId
666
        );
667
668
        $this->vettedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
669
    }
670
671 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...
672
    {
673
        $secondFactor = UnverifiedSecondFactor::create(
674
            $event->secondFactorId,
675
            $this,
676
            new SecondFactorType('yubikey'),
677
            $event->yubikeyPublicId,
678
            $event->emailVerificationWindow,
679
            $event->emailVerificationNonce
680
        );
681
682
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
683
    }
684
685 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...
686
    {
687
        $secondFactor = UnverifiedSecondFactor::create(
688
            $event->secondFactorId,
689
            $this,
690
            new SecondFactorType('sms'),
691
            $event->phoneNumber,
692
            $event->emailVerificationWindow,
693
            $event->emailVerificationNonce
694
        );
695
696
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
697
    }
698
699 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...
700
    {
701
        $secondFactor = UnverifiedSecondFactor::create(
702
            $event->secondFactorId,
703
            $this,
704
            new SecondFactorType((string) $event->stepupProvider),
705
            $event->gssfId,
706
            $event->emailVerificationWindow,
707
            $event->emailVerificationNonce
708
        );
709
710
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
711
    }
712
713 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...
714
    {
715
        $secondFactor = UnverifiedSecondFactor::create(
716
            $event->secondFactorId,
717
            $this,
718
            new SecondFactorType('u2f'),
719
            $event->keyHandle,
720
            $event->emailVerificationWindow,
721
            $event->emailVerificationNonce
722
        );
723
724
        $this->unverifiedSecondFactors->set((string) $secondFactor->getId(), $secondFactor);
725
    }
726
727
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event)
728
    {
729
        $secondFactorId = (string) $event->secondFactorId;
730
731
        /** @var UnverifiedSecondFactor $unverified */
732
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
733
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
734
735
        $this->unverifiedSecondFactors->remove($secondFactorId);
736
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
737
    }
738
739
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event)
740
    {
741
        $secondFactorId = (string) $event->secondFactorId;
742
743
        /** @var VerifiedSecondFactor $verified */
744
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
745
        $vetted = $verified->asVetted();
746
747
        $this->verifiedSecondFactors->remove($secondFactorId);
748
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
749
    }
750
751
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event)
752
    {
753
        $this->unverifiedSecondFactors->remove((string) $event->secondFactorId);
754
    }
755
756
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
757
        CompliedWithUnverifiedSecondFactorRevocationEvent $event
758
    ) {
759
        $this->unverifiedSecondFactors->remove((string) $event->secondFactorId);
760
    }
761
762
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event)
763
    {
764
        $this->verifiedSecondFactors->remove((string) $event->secondFactorId);
765
    }
766
767
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
768
        CompliedWithVerifiedSecondFactorRevocationEvent $event
769
    ) {
770
        $this->verifiedSecondFactors->remove((string) $event->secondFactorId);
771
    }
772
773
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event)
774
    {
775
        $this->vettedSecondFactors->remove((string) $event->secondFactorId);
776
    }
777
778
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
779
        CompliedWithVettedSecondFactorRevocationEvent $event
780
    ) {
781
        $this->vettedSecondFactors->remove((string) $event->secondFactorId);
782
    }
783
784
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event)
785
    {
786
        $this->registrationAuthority = RegistrationAuthority::accreditWith(
787
            $event->registrationAuthorityRole,
788
            $event->location,
789
            $event->contactInformation
790
        );
791
    }
792
793
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event)
794
    {
795
        $this->registrationAuthority = RegistrationAuthority::accreditWith(
796
            $event->registrationAuthorityRole,
797
            $event->location,
798
            $event->contactInformation
799
        );
800
    }
801
802
    protected function applyRegistrationAuthorityInformationAmendedEvent(
803
        RegistrationAuthorityInformationAmendedEvent $event
804
    ) {
805
        $this->registrationAuthority->amendInformation($event->location, $event->contactInformation);
806
    }
807
808
    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...
809
    {
810
        $this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
811
    }
812
813
    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...
814
    {
815
        $this->registrationAuthority->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
816
    }
817
818
    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...
819
    {
820
        $this->registrationAuthority = null;
821
    }
822
823
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event)
824
    {
825
        $this->preferredLocale = $event->preferredLocale;
826
    }
827
828
    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...
829
    {
830
        $this->commonName = CommonName::unknown();
831
        $this->email      = Email::unknown();
832
        $this->forgotten  = true;
833
    }
834
835
    public function getAggregateRootId()
836
    {
837
        return $this->id;
838
    }
839
840
    protected function getChildEntities()
841
    {
842
        return array_merge(
843
            $this->unverifiedSecondFactors->getValues(),
844
            $this->verifiedSecondFactors->getValues(),
845
            $this->vettedSecondFactors->getValues()
846
        );
847
    }
848
849
    /**
850
     * @throws DomainException
851
     */
852
    private function assertNotForgotten()
853
    {
854
        if ($this->forgotten) {
855
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
856
        }
857
    }
858
859
    /**
860
     * @throws DomainException
861
     */
862
    private function assertUserMayAddSecondFactor()
863
    {
864
        if (count($this->unverifiedSecondFactors) +
865
            count($this->verifiedSecondFactors) +
866
            count($this->vettedSecondFactors) >= $this->maxNumberOfTokens
867
        ) {
868
            throw new DomainException(
869
                sprintf('User may not have more than %d token(s)', $this->maxNumberOfTokens)
870
            );
871
        }
872
    }
873
874
    public function getId()
875
    {
876
        return $this->id;
877
    }
878
879
    /**
880
     * @return NameId
881
     */
882
    public function getNameId()
883
    {
884
        return $this->nameId;
885
    }
886
887
    /**
888
     * @return Institution
889
     */
890
    public function getInstitution()
891
    {
892
        return $this->institution;
893
    }
894
895
    public function getCommonName()
896
    {
897
        return $this->commonName;
898
    }
899
900
    public function getEmail()
901
    {
902
        return $this->email;
903
    }
904
905
    public function getPreferredLocale()
906
    {
907
        return $this->preferredLocale;
908
    }
909
910
    /**
911
     * @param SecondFactorId $secondFactorId
912
     * @return VerifiedSecondFactor|null
913
     */
914
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId)
915
    {
916
        return $this->verifiedSecondFactors->get((string) $secondFactorId);
917
    }
918
}
919