Completed
Push — master ( 5467e4...183ea4 )
by Boy
05:06 queued 01:02
created

Identity::getEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 4
rs 10
c 1
b 0
f 0
cc 1
eloc 2
nc 1
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\Exception\DomainException;
23
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
24
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
25
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
26
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
27
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
28
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
29
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
30
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
31
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
32
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
33
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
34
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
35
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
36
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
37
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
38
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
39
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
40
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
41
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
42
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
43
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
44
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
45
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
46
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
47
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
48
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
49
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
50
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
51
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
52
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
53
use Surfnet\Stepup\Identity\Value\CommonName;
54
use Surfnet\Stepup\Identity\Value\ContactInformation;
55
use Surfnet\Stepup\Identity\Value\DocumentNumber;
56
use Surfnet\Stepup\Identity\Value\Email;
57
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow;
58
use Surfnet\Stepup\Identity\Value\GssfId;
59
use Surfnet\Stepup\Identity\Value\IdentityId;
60
use Surfnet\Stepup\Identity\Value\Institution;
61
use Surfnet\Stepup\Identity\Value\Locale;
62
use Surfnet\Stepup\Identity\Value\Location;
63
use Surfnet\Stepup\Identity\Value\NameId;
64
use Surfnet\Stepup\Identity\Value\PhoneNumber;
65
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole;
66
use Surfnet\Stepup\Identity\Value\SecondFactorId;
67
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
68
use Surfnet\Stepup\Identity\Value\StepupProvider;
69
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
70
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
71
use Surfnet\Stepup\Token\TokenGenerator;
72
use Surfnet\StepupBundle\Value\SecondFactorType;
73
74
/**
75
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
76
 * @SuppressWarnings(PHPMD.TooManyMethods)
77
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
78
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
79
 */
80
class Identity extends EventSourcedAggregateRoot implements IdentityApi
81
{
82
    /**
83
     * @var IdentityId
84
     */
85
    private $id;
86
87
    /**
88
     * @var Institution
89
     */
90
    private $institution;
91
92
    /**
93
     * @var NameId
94
     */
95
    private $nameId;
96
97
    /**
98
     * @var \Surfnet\Stepup\Identity\Value\CommonName
99
     */
100
    private $commonName;
101
102
    /**
103
     * @var \Surfnet\Stepup\Identity\Value\Email
104
     */
105
    private $email;
106
107
    /**
108
     * @var SecondFactorCollection|UnverifiedSecondFactor[]
109
     */
110
    private $unverifiedSecondFactors;
111
112
    /**
113
     * @var SecondFactorCollection|VerifiedSecondFactor[]
114
     */
115
    private $verifiedSecondFactors;
116
117
    /**
118
     * @var SecondFactorCollection|VettedSecondFactor[]
119
     */
120
    private $vettedSecondFactors;
121
122
    /**
123
     * @var RegistrationAuthority
124
     */
125
    private $registrationAuthority;
126
127
    /**
128
     * @var Locale
129
     */
130
    private $preferredLocale;
131
132
    /**
133
     * @var boolean
134
     */
135
    private $forgotten;
136
137
    public static function create(
138
        IdentityId $id,
139
        Institution $institution,
140
        NameId $nameId,
141
        CommonName $commonName,
142
        Email $email,
143
        Locale $preferredLocale
144
    ) {
145
        $identity = new self();
146
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
147
148
        return $identity;
149
    }
150
151
    final public function __construct()
152
    {
153
    }
154
155
    public function rename(CommonName $commonName)
156
    {
157
        $this->assertNotForgotten();
158
159
        if ($this->commonName->equals($commonName)) {
160
            return;
161
        }
162
163
        $this->commonName = $commonName;
164
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
165
    }
166
167
    public function changeEmail(Email $email)
168
    {
169
        $this->assertNotForgotten();
170
171
        if ($this->email->equals($email)) {
172
            return;
173
        }
174
175
        $this->email = $email;
176
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
177
    }
178
179
    public function bootstrapYubikeySecondFactor(SecondFactorId $secondFactorId, YubikeyPublicId $yubikeyPublicId)
180
    {
181
        $this->assertNotForgotten();
182
        $this->assertUserMayAddSecondFactor();
183
184
        $this->apply(
185
            new YubikeySecondFactorBootstrappedEvent(
186
                $this->id,
187
                $this->nameId,
188
                $this->institution,
189
                $this->commonName,
190
                $this->email,
191
                $this->preferredLocale,
192
                $secondFactorId,
193
                $yubikeyPublicId
194
            )
195
        );
196
    }
197
198 View Code Duplication
    public function provePossessionOfYubikey(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
199
        SecondFactorId $secondFactorId,
200
        YubikeyPublicId $yubikeyPublicId,
201
        EmailVerificationWindow $emailVerificationWindow
202
    ) {
203
        $this->assertNotForgotten();
204
        $this->assertUserMayAddSecondFactor();
205
206
        $this->apply(
207
            new YubikeyPossessionProvenEvent(
208
                $this->id,
209
                $this->institution,
210
                $secondFactorId,
211
                $yubikeyPublicId,
212
                $emailVerificationWindow,
213
                TokenGenerator::generateNonce(),
214
                $this->commonName,
215
                $this->email,
216
                $this->preferredLocale
217
            )
218
        );
219
    }
220
221 View Code Duplication
    public function provePossessionOfPhone(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
222
        SecondFactorId $secondFactorId,
223
        PhoneNumber $phoneNumber,
224
        EmailVerificationWindow $emailVerificationWindow
225
    ) {
226
        $this->assertNotForgotten();
227
        $this->assertUserMayAddSecondFactor();
228
229
        $this->apply(
230
            new PhonePossessionProvenEvent(
231
                $this->id,
232
                $this->institution,
233
                $secondFactorId,
234
                $phoneNumber,
235
                $emailVerificationWindow,
236
                TokenGenerator::generateNonce(),
237
                $this->commonName,
238
                $this->email,
239
                $this->preferredLocale
240
            )
241
        );
242
    }
243
244 View Code Duplication
    public function provePossessionOfGssf(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
245
        SecondFactorId $secondFactorId,
246
        StepupProvider $provider,
247
        GssfId $gssfId,
248
        EmailVerificationWindow $emailVerificationWindow
249
    ) {
250
        $this->assertNotForgotten();
251
        $this->assertUserMayAddSecondFactor();
252
253
        $this->apply(
254
            new GssfPossessionProvenEvent(
255
                $this->id,
256
                $this->institution,
257
                $secondFactorId,
258
                $provider,
259
                $gssfId,
260
                $emailVerificationWindow,
261
                TokenGenerator::generateNonce(),
262
                $this->commonName,
263
                $this->email,
264
                $this->preferredLocale
265
            )
266
        );
267
    }
268
269 View Code Duplication
    public function provePossessionOfU2fDevice(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
270
        SecondFactorId $secondFactorId,
271
        U2fKeyHandle $keyHandle,
272
        EmailVerificationWindow $emailVerificationWindow
273
    ) {
274
        $this->assertNotForgotten();
275
        $this->assertUserMayAddSecondFactor();
276
277
        $this->apply(
278
            new U2fDevicePossessionProvenEvent(
279
                $this->id,
280
                $this->institution,
281
                $secondFactorId,
282
                $keyHandle,
283
                $emailVerificationWindow,
284
                TokenGenerator::generateNonce(),
285
                $this->commonName,
286
                $this->email,
287
                $this->preferredLocale
288
            )
289
        );
290
    }
291
292
    public function verifyEmail($verificationNonce)
293
    {
294
        $this->assertNotForgotten();
295
296
        $secondFactorToVerify = null;
297
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
298
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
299
            if ($secondFactor->hasNonce($verificationNonce)) {
300
                $secondFactorToVerify = $secondFactor;
301
            }
302
        }
303
304
        if (!$secondFactorToVerify) {
305
            throw new DomainException(
306
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce'
307
            );
308
        }
309
310
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
311
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
312
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
313
        }
314
315
        $secondFactorToVerify->verifyEmail();
316
    }
317
318
    public function vetSecondFactor(
319
        IdentityApi $registrant,
320
        SecondFactorId $registrantsSecondFactorId,
321
        SecondFactorType $registrantsSecondFactorType,
322
        SecondFactorIdentifier $registrantsSecondFactorIdentifier,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $registrantsSecondFactorIdentifier exceeds the maximum configured length of 30.

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

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