Completed
Push — develop ( 4b76b9...136345 )
by
unknown
07:15 queued 01:31
created

applyPhonePossessionProvenAndVerifiedEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 13
Ratio 100 %

Importance

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