applyUnverifiedSecondFactorRevokedEvent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 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\Configuration\InstitutionConfiguration;
23
use Surfnet\Stepup\Configuration\Value\Institution as ConfigurationInstitution;
24
use Surfnet\Stepup\DateTime\DateTime;
25
use Surfnet\Stepup\Exception\DomainException;
26
use Surfnet\Stepup\Helper\SecondFactorProvePossessionHelper;
27
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
28
use Surfnet\Stepup\Identity\Collection\VettingTypeHintCollection;
29
use Surfnet\Stepup\Identity\Entity\RecoveryToken as RecoveryTokenEntity;
30
use Surfnet\Stepup\Identity\Entity\RecoveryTokenCollection;
31
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
32
use Surfnet\Stepup\Identity\Entity\RegistrationAuthorityCollection;
33
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
34
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
35
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
36
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
37
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
38
use Surfnet\Stepup\Identity\Event\AppointedAsRaaForInstitutionEvent;
39
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
40
use Surfnet\Stepup\Identity\Event\AppointedAsRaForInstitutionEvent;
41
use Surfnet\Stepup\Identity\Event\CompliedWithRecoveryCodeRevocationEvent;
42
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
43
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
44
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
45
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
46
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenAndVerifiedEvent;
47
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
48
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
49
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaForInstitutionEvent;
50
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
51
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaForInstitutionEvent;
52
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
53
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
54
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
55
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
56
use Surfnet\Stepup\Identity\Event\IdentityRestoredEvent;
57
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
58
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenAndVerifiedEvent;
59
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
60
use Surfnet\Stepup\Identity\Event\PhoneRecoveryTokenPossessionProvenEvent;
61
use Surfnet\Stepup\Identity\Event\RecoveryTokenRevokedEvent;
62
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
63
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedForInstitutionEvent;
64
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
65
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedForInstitutionEvent;
66
use Surfnet\Stepup\Identity\Event\SafeStoreSecretRecoveryTokenPossessionPromisedEvent;
67
use Surfnet\Stepup\Identity\Event\SecondFactorMigratedEvent;
68
use Surfnet\Stepup\Identity\Event\SecondFactorMigratedToEvent;
69
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
70
use Surfnet\Stepup\Identity\Event\SecondFactorVettedWithoutTokenProofOfPossession;
71
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenAndVerifiedEvent;
72
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
73
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
74
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
75
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
76
use Surfnet\Stepup\Identity\Event\VettedSecondFactorsAllRevokedEvent;
77
use Surfnet\Stepup\Identity\Event\VettingTypeHintsSavedEvent;
78
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenAndVerifiedEvent;
79
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
80
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
81
use Surfnet\Stepup\Identity\Value\CommonName;
82
use Surfnet\Stepup\Identity\Value\ContactInformation;
83
use Surfnet\Stepup\Identity\Value\DocumentNumber;
84
use Surfnet\Stepup\Identity\Value\Email;
85
use Surfnet\Stepup\Identity\Value\EmailVerificationWindow;
0 ignored issues
show
Bug introduced by
The type Surfnet\Stepup\Identity\...EmailVerificationWindow was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
86
use Surfnet\Stepup\Identity\Value\GssfId;
87
use Surfnet\Stepup\Identity\Value\IdentityId;
88
use Surfnet\Stepup\Identity\Value\Institution;
89
use Surfnet\Stepup\Identity\Value\Locale;
0 ignored issues
show
Bug introduced by
The type Surfnet\Stepup\Identity\Value\Locale was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
90
use Surfnet\Stepup\Identity\Value\Location;
91
use Surfnet\Stepup\Identity\Value\NameId;
92
use Surfnet\Stepup\Identity\Value\OnPremiseVettingType;
93
use Surfnet\Stepup\Identity\Value\PhoneNumber;
94
use Surfnet\Stepup\Identity\Value\RecoveryTokenId;
95
use Surfnet\Stepup\Identity\Value\RecoveryTokenType;
96
use Surfnet\Stepup\Identity\Value\RegistrationAuthorityRole;
0 ignored issues
show
Bug introduced by
The type Surfnet\Stepup\Identity\...gistrationAuthorityRole was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
97
use Surfnet\Stepup\Identity\Value\SafeStore;
98
use Surfnet\Stepup\Identity\Value\SecondFactorId;
99
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
100
use Surfnet\Stepup\Identity\Value\SelfAssertedRegistrationVettingType;
101
use Surfnet\Stepup\Identity\Value\SelfVetVettingType;
102
use Surfnet\Stepup\Identity\Value\StepupProvider;
103
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
104
use Surfnet\Stepup\Identity\Value\UnknownVettingType;
105
use Surfnet\Stepup\Identity\Value\VettingType;
106
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
107
use Surfnet\Stepup\Token\TokenGenerator;
108
use Surfnet\StepupBundle\Security\OtpGenerator;
109
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
110
use Surfnet\StepupBundle\Value\Loa;
111
use Surfnet\StepupBundle\Value\SecondFactorType;
112
113
/**
114
 * @SuppressWarnings("PHPMD.CouplingBetweenObjects")
115
 * @SuppressWarnings("PHPMD.TooManyMethods")
116
 * @SuppressWarnings("PHPMD.TooManyPublicMethods")
117
 * @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
118
 * @SuppressWarnings("PHPMD.ExcessiveClassLength")
119
 */
120
class Identity extends EventSourcedAggregateRoot implements IdentityApi
121
{
122
    /**
123
     * @var IdentityId
124
     */
125
    private IdentityId $id;
126
127
    /**
128
     * @var Institution
129
     */
130
    private Institution $institution;
131
132
    /**
133
     * @var NameId
134
     */
135
    private NameId $nameId;
136
137
    /**
138
     * @var CommonName
139
     */
140
    private CommonName $commonName;
141
142
    /**
143
     * @var Email
144
     */
145
    private Email $email;
146
147
    private ?SecondFactorCollection $unverifiedSecondFactors = null;
148
149
    private ?SecondFactorCollection $verifiedSecondFactors = null;
150
151
    private ?SecondFactorCollection $vettedSecondFactors = null;
152
153
    private ?RegistrationAuthorityCollection $registrationAuthorities = null;
154
155
    /**
156
     * @var Locale
157
     */
158
    private Locale $preferredLocale;
159
160
    private ?bool $forgotten = null;
161
162
    private ?RecoveryTokenCollection $recoveryTokens = null;
163
164
    public static function create(
165
        IdentityId $id,
166
        Institution $institution,
167
        NameId $nameId,
168
        CommonName $commonName,
169
        Email $email,
170
        Locale $preferredLocale,
171
    ): self {
172
        $identity = new self();
173
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
174
175
        return $identity;
176
    }
177
178
    final public function __construct()
179
    {
180
    }
181
182
    public function rename(CommonName $commonName): void
183
    {
184
        $this->assertNotForgotten();
185
186
        if ($this->commonName->equals($commonName)) {
187
            return;
188
        }
189
190
        $this->commonName = $commonName;
191
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
192
    }
193
194
    public function changeEmail(Email $email): void
195
    {
196
        $this->assertNotForgotten();
197
198
        if ($this->email->equals($email)) {
199
            return;
200
        }
201
202
        $this->email = $email;
203
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
204
    }
205
206
    public function bootstrapYubikeySecondFactor(
207
        SecondFactorId  $secondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorId"; 2 found
Loading history...
208
        YubikeyPublicId $yubikeyPublicId,
209
        int             $maxNumberOfTokens,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$maxNumberOfTokens"; 13 found
Loading history...
210
    ): void {
211
        $this->assertNotForgotten();
212
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
213
        $this->assertTokenTypeNotAlreadyRegistered(new SecondFactorType('yubikey'));
214
215
        $this->apply(
216
            new YubikeySecondFactorBootstrappedEvent(
217
                $this->id,
218
                $this->nameId,
219
                $this->institution,
220
                $this->commonName,
221
                $this->email,
222
                $this->preferredLocale,
223
                $secondFactorId,
224
                $yubikeyPublicId,
225
            ),
226
        );
227
    }
228
229
    public function provePossessionOfYubikey(
230
        SecondFactorId          $secondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorId"; 10 found
Loading history...
231
        YubikeyPublicId         $yubikeyPublicId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$yubikeyPublicId"; 9 found
Loading history...
232
        bool                    $emailVerificationRequired,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$emailVerificationRequired"; 20 found
Loading history...
233
        EmailVerificationWindow $emailVerificationWindow,
234
        int                     $maxNumberOfTokens,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$maxNumberOfTokens"; 21 found
Loading history...
235
    ): void {
236
        $this->assertNotForgotten();
237
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
238
        $this->assertTokenTypeNotAlreadyRegistered(new SecondFactorType('yubikey'));
239
240
        if ($emailVerificationRequired) {
241
            $emailVerificationNonce = TokenGenerator::generateNonce();
242
243
            $this->apply(
244
                new YubikeyPossessionProvenEvent(
245
                    $this->id,
246
                    $this->institution,
247
                    $secondFactorId,
248
                    $yubikeyPublicId,
249
                    $emailVerificationRequired,
250
                    $emailVerificationWindow,
251
                    $emailVerificationNonce,
252
                    $this->commonName,
253
                    $this->email,
254
                    $this->preferredLocale,
255
                ),
256
            );
257
        } else {
258
            $this->apply(
259
                new YubikeyPossessionProvenAndVerifiedEvent(
260
                    $this->id,
261
                    $this->institution,
262
                    $secondFactorId,
263
                    $yubikeyPublicId,
264
                    $this->commonName,
265
                    $this->email,
266
                    $this->preferredLocale,
267
                    DateTime::now(),
268
                    OtpGenerator::generate(8),
269
                ),
270
            );
271
        }
272
    }
273
274
    public function provePossessionOfPhone(
275
        SecondFactorId          $secondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorId"; 10 found
Loading history...
276
        PhoneNumber             $phoneNumber,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$phoneNumber"; 13 found
Loading history...
277
        bool                    $emailVerificationRequired,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$emailVerificationRequired"; 20 found
Loading history...
278
        EmailVerificationWindow $emailVerificationWindow,
279
        int                     $maxNumberOfTokens,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$maxNumberOfTokens"; 21 found
Loading history...
280
    ): void {
281
        $this->assertNotForgotten();
282
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
283
        $this->assertTokenTypeNotAlreadyRegistered(new SecondFactorType('sms'));
284
285
        if ($emailVerificationRequired) {
286
            $emailVerificationNonce = TokenGenerator::generateNonce();
287
288
            $this->apply(
289
                new PhonePossessionProvenEvent(
290
                    $this->id,
291
                    $this->institution,
292
                    $secondFactorId,
293
                    $phoneNumber,
294
                    $emailVerificationRequired,
295
                    $emailVerificationWindow,
296
                    $emailVerificationNonce,
297
                    $this->commonName,
298
                    $this->email,
299
                    $this->preferredLocale,
300
                ),
301
            );
302
        } else {
303
            $this->apply(
304
                new PhonePossessionProvenAndVerifiedEvent(
305
                    $this->id,
306
                    $this->institution,
307
                    $secondFactorId,
308
                    $phoneNumber,
309
                    $this->commonName,
310
                    $this->email,
311
                    $this->preferredLocale,
312
                    DateTime::now(),
313
                    OtpGenerator::generate(8),
314
                ),
315
            );
316
        }
317
    }
318
319
    public function provePossessionOfPhoneRecoveryToken(
320
        RecoveryTokenId $recoveryTokenId,
321
        PhoneNumber $phoneNumber,
322
    ): void {
323
        $this->assertNotForgotten();
324
        $this->assertUserMayAddRecoveryToken(RecoveryTokenType::sms());
325
        $this->apply(
326
            new PhoneRecoveryTokenPossessionProvenEvent(
327
                $this->id,
328
                $this->institution,
329
                $recoveryTokenId,
330
                $phoneNumber,
331
                $this->commonName,
332
                $this->email,
333
                $this->preferredLocale,
334
            ),
335
        );
336
    }
337
338
339
    public function promisePossessionOfSafeStoreSecretRecoveryToken(RecoveryTokenId $tokenId, SafeStore $secret): void
340
    {
341
        $this->assertNotForgotten();
342
        $this->assertUserMayAddRecoveryToken(RecoveryTokenType::safeStore());
343
        $this->apply(
344
            new SafeStoreSecretRecoveryTokenPossessionPromisedEvent(
345
                $this->id,
346
                $this->institution,
347
                $tokenId,
348
                $secret,
349
                $this->commonName,
350
                $this->email,
351
                $this->preferredLocale,
352
            ),
353
        );
354
    }
355
356
    public function saveVettingTypeHints(Institution $institution, VettingTypeHintCollection $hints): void
357
    {
358
        $this->assertNotForgotten();
359
        $this->apply(
360
            new VettingTypeHintsSavedEvent(
361
                $this->id,
362
                $this->institution,
363
                $hints,
364
                $institution,
365
            ),
366
        );
367
    }
368
369
    public function provePossessionOfGssf(
370
        SecondFactorId          $secondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorId"; 10 found
Loading history...
371
        StepupProvider          $provider,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$provider"; 10 found
Loading history...
372
        GssfId                  $gssfId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$gssfId"; 18 found
Loading history...
373
        bool                    $emailVerificationRequired,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$emailVerificationRequired"; 20 found
Loading history...
374
        EmailVerificationWindow $emailVerificationWindow,
375
        int                     $maxNumberOfTokens,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$maxNumberOfTokens"; 21 found
Loading history...
376
    ): void {
377
        $this->assertNotForgotten();
378
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
379
        $this->assertTokenTypeNotAlreadyRegistered(new SecondFactorType($provider->getStepupProvider()));
380
381
        if ($emailVerificationRequired) {
382
            $emailVerificationNonce = TokenGenerator::generateNonce();
383
384
            $this->apply(
385
                new GssfPossessionProvenEvent(
386
                    $this->id,
387
                    $this->institution,
388
                    $secondFactorId,
389
                    $provider,
390
                    $gssfId,
391
                    $emailVerificationRequired,
392
                    $emailVerificationWindow,
393
                    $emailVerificationNonce,
394
                    $this->commonName,
395
                    $this->email,
396
                    $this->preferredLocale,
397
                ),
398
            );
399
        } else {
400
            $this->apply(
401
                new GssfPossessionProvenAndVerifiedEvent(
402
                    $this->id,
403
                    $this->institution,
404
                    $secondFactorId,
405
                    $provider,
406
                    $gssfId,
407
                    $this->commonName,
408
                    $this->email,
409
                    $this->preferredLocale,
410
                    DateTime::now(),
411
                    OtpGenerator::generate(8),
412
                ),
413
            );
414
        }
415
    }
416
417
    /**
418
     * @deprecated Built in U2F support is dropped from StepUp, this was not removed to support event replay
419
     */
420
    public function provePossessionOfU2fDevice(
421
        SecondFactorId          $secondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorId"; 10 found
Loading history...
422
        U2fKeyHandle            $keyHandle,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$keyHandle"; 12 found
Loading history...
423
        bool                    $emailVerificationRequired,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$emailVerificationRequired"; 20 found
Loading history...
424
        EmailVerificationWindow $emailVerificationWindow,
425
        int                     $maxNumberOfTokens,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$maxNumberOfTokens"; 21 found
Loading history...
426
    ): void {
427
        $this->assertNotForgotten();
428
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
429
        $this->assertTokenTypeNotAlreadyRegistered(new SecondFactorType('u2f'));
430
431
        if ($emailVerificationRequired) {
432
            $emailVerificationNonce = TokenGenerator::generateNonce();
433
434
            $this->apply(
435
                new U2fDevicePossessionProvenEvent(
0 ignored issues
show
Deprecated Code introduced by
The class Surfnet\Stepup\Identity\...cePossessionProvenEvent has been deprecated: Built in U2F support is dropped from StepUp, this Event was not removed to support event replay ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

435
                /** @scrutinizer ignore-deprecated */ new U2fDevicePossessionProvenEvent(
Loading history...
436
                    $this->id,
437
                    $this->institution,
438
                    $secondFactorId,
439
                    $keyHandle,
440
                    $emailVerificationRequired,
441
                    $emailVerificationWindow,
442
                    $emailVerificationNonce,
443
                    $this->commonName,
444
                    $this->email,
445
                    $this->preferredLocale,
446
                ),
447
            );
448
        } else {
449
            $this->apply(
450
                new U2fDevicePossessionProvenAndVerifiedEvent(
0 ignored issues
show
Deprecated Code introduced by
The class Surfnet\Stepup\Identity\...nProvenAndVerifiedEvent has been deprecated: Built in U2F support is dropped from StepUp, this Event was not removed to support event replay ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

450
                /** @scrutinizer ignore-deprecated */ new U2fDevicePossessionProvenAndVerifiedEvent(
Loading history...
451
                    $this->id,
452
                    $this->institution,
453
                    $secondFactorId,
454
                    $keyHandle,
455
                    $this->commonName,
456
                    $this->email,
457
                    $this->preferredLocale,
458
                    DateTime::now(),
459
                    OtpGenerator::generate(8),
460
                ),
461
            );
462
        }
463
    }
464
465
    public function verifyEmail(string $verificationNonce): void
466
    {
467
        $this->assertNotForgotten();
468
469
        $secondFactorToVerify = null;
470
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
471
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
472
            if ($secondFactor->hasNonce($verificationNonce)) {
473
                $secondFactorToVerify = $secondFactor;
474
            }
475
        }
476
477
        if (!$secondFactorToVerify) {
478
            throw new DomainException(
479
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce',
480
            );
481
        }
482
483
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
484
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
485
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
486
        }
487
488
        $secondFactorToVerify->verifyEmail();
489
    }
490
491
    /**
492
     * @SuppressWarnings("PHPMD.ExcessiveParameterList")
493
     */
494
    public function vetSecondFactor(
495
        IdentityApi                       $registrant,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$registrant"; 23 found
Loading history...
496
        SecondFactorId                    $registrantsSecondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$registrantsSecondFactorId"; 20 found
Loading history...
497
        SecondFactorType                  $registrantsSecondFactorType,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$registrantsSecondFactorType"; 18 found
Loading history...
498
        SecondFactorIdentifier            $registrantsSecondFactorIdentifier,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$registrantsSecondFactorIdentifier"; 12 found
Loading history...
499
        string                            $registrationCode,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$registrationCode"; 28 found
Loading history...
500
        DocumentNumber                    $documentNumber,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$documentNumber"; 20 found
Loading history...
501
        bool                              $identityVerified,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$identityVerified"; 30 found
Loading history...
502
        SecondFactorTypeService           $secondFactorTypeService,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorTypeService"; 11 found
Loading history...
503
        SecondFactorProvePossessionHelper $secondFactorProvePossessionHelper,
504
        bool                              $provePossessionSkipped,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$provePossessionSkipped"; 30 found
Loading history...
505
    ): void {
506
        $this->assertNotForgotten();
507
508
        /** The vetted second factor collection can determine highest loa based on the vetting type,
509
         * the other can not (as the verified and unverified second factors do not have a vetting type)
510
         * And the vetting type is used to determine if the LoA is diminished (in case of a self
511
         * asserted token registration)
512
         */
513
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
514
        $secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa(
0 ignored issues
show
Bug introduced by
The method getSecondFactorWithHighestLoa() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

514
        /** @scrutinizer ignore-call */ 
515
        $secondFactorWithHighestLoa = $this->vettedSecondFactors->getSecondFactorWithHighestLoa(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
515
            $secondFactorTypeService,
516
        );
517
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
518
519
        if (!$registrantsSecondFactor instanceof VerifiedSecondFactor) {
520
            throw new DomainException(
521
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId),
522
            );
523
        }
524
525
        if ($secondFactorWithHighestLoa === null) {
526
            throw new DomainException(
527
                sprintf(
528
                    'Vetting failed: authority %s has %d vetted second factors!',
529
                    $this->id,
530
                    count($this->vettedSecondFactors),
0 ignored issues
show
Bug introduced by
It seems like $this->vettedSecondFactors can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

530
                    count(/** @scrutinizer ignore-type */ $this->vettedSecondFactors),
Loading history...
531
                ),
532
            );
533
        }
534
535
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo(
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
536
            $registrantsSecondFactor,
537
            $secondFactorTypeService,
538
        )) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line control structure must be indented at least once; expected at least 12 spaces, but found 8
Loading history...
Coding Style introduced by
The closing parenthesis of a multi-line control structure must be on the line after the last expression
Loading history...
539
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
540
        }
541
542
        if (!$identityVerified) {
543
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
544
        }
545
546
        if ($provePossessionSkipped && !$secondFactorProvePossessionHelper->canSkipProvePossession(
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
547
            $registrantsSecondFactorType,
548
        )) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line control structure must be indented at least once; expected at least 12 spaces, but found 8
Loading history...
Coding Style introduced by
The closing parenthesis of a multi-line control structure must be on the line after the last expression
Loading history...
549
            throw new DomainException(
550
                sprintf(
551
                    "The possession of registrants second factor with ID '%s' of type '%s' has to be physically proven",
552
                    $registrantsSecondFactorId,
553
                    $registrantsSecondFactorType->getSecondFactorType(),
554
                ),
555
            );
556
        }
557
558
        $registrant->complyWithVettingOfSecondFactor(
559
            $registrantsSecondFactorId,
560
            $registrantsSecondFactorType,
561
            $registrantsSecondFactorIdentifier,
562
            $registrationCode,
563
            $documentNumber,
564
            $provePossessionSkipped,
565
        );
566
    }
567
568
    public function registerSelfAssertedSecondFactor(
569
        SecondFactorIdentifier $secondFactorIdentifier,
570
        SecondFactorTypeService $secondFactorTypeService,
571
        RecoveryTokenId $recoveryTokenId,
572
    ): void {
573
        $this->assertNotForgotten();
574
        $this->assertSelfAssertedTokenRegistrationAllowed();
575
576
        try {
577
            $recoveryToken = $this->recoveryTokens->get($recoveryTokenId);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

577
            /** @scrutinizer ignore-call */ 
578
            $recoveryToken = $this->recoveryTokens->get($recoveryTokenId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
578
        } catch (DomainException) {
579
            throw new DomainException(
580
                sprintf('Recovery token used during registration is not possessed by identity %s', (string)$this->id),
581
            );
582
        }
583
584
        $registeringSecondFactor = null;
585
        foreach ($this->verifiedSecondFactors as $secondFactor) {
586
            if ($secondFactorIdentifier->equals($secondFactor->getIdentifier())) {
587
                $registeringSecondFactor = $secondFactor;
588
            }
589
        }
590
591
        if ($registeringSecondFactor === null) {
0 ignored issues
show
introduced by
The condition $registeringSecondFactor === null is always true.
Loading history...
592
            throw new DomainException(
593
                sprintf(
594
                    'Registering second factor of type %s with ID %s does not exist',
595
                    $secondFactorIdentifier::class,
596
                    $secondFactorIdentifier->getValue(),
597
                ),
598
            );
599
        }
600
        $registeringSecondFactor->vet(true, new SelfAssertedRegistrationVettingType($recoveryToken->getTokenId()));
601
    }
602
603
    /**
604
     * Two self-vet scenarios are dealt with
605
     *
606
     * 1. A regular self-vet action. Where an on premise token is used to vet another token
607
     *    from the comfort of the identity's SelfService application. In other words, self vetting
608
     *    allows the identity to activate a second/third/.. token without visiting the service desk
609
     *
610
     * 2. A variation on 1: but here a self-asserted token is used to activate the verified token.
611
     *    This new token will inherit the LoA of the self-asserted token. Effectively giving it a
612
     *    LoA 1.5 level.
613
     *
614
     * The code below uses the following terminology
615
     *
616
     *   RegisteringSecondFactor: This is the verified second factor that is to be activated
617
     *                            using the self-vet vetting type
618
     *   AuthoringSecondFactor:   The vetted token, used to activate (vet) the RegisteringSecondFactor
619
     *   IsSelfVetUsingSAT:       Is self-vetting using a self-asserted token allowed for this
620
     *                            self-vet scenario? All existing vetted tokens must be of the
621
     *                            self-asserted vetting type.
622
     *
623
     */
624
    public function selfVetSecondFactor(
625
        Loa $authoringSecondFactorLoa,
626
        string $registrationCode,
627
        SecondFactorIdentifier $secondFactorIdentifier,
628
        SecondFactorTypeService $secondFactorTypeService,
629
    ): void {
630
        $this->assertNotForgotten();
631
        $registeringSecondFactor = null;
632
        foreach ($this->verifiedSecondFactors as $secondFactor) {
633
            /** @var VerifiedSecondFactor $secondFactor */
634
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
635
                $registeringSecondFactor = $secondFactor;
636
            }
637
        }
638
639
        if ($registeringSecondFactor === null) {
640
            throw new DomainException(
641
                sprintf(
642
                    'Registrant second factor of type %s with ID %s does not exist',
643
                    $secondFactorIdentifier::class,
644
                    $secondFactorIdentifier->getValue(),
645
                ),
646
            );
647
        }
648
649
        if (!$registeringSecondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
650
            throw new DomainException('The verified second factors registration code or identifier do not match.');
651
        }
652
653
        $selfVettingIsAllowed = $authoringSecondFactorLoa->levelIsHigherOrEqualTo(
654
            $registeringSecondFactor->getLoaLevel($secondFactorTypeService),
655
        );
656
657
        // Was the authorizing token a self-asserted token (does it have LoA 1.5?)
658
        $isSelfVetUsingSAT = $authoringSecondFactorLoa->getLevel() === Loa::LOA_SELF_VETTED;
659
660
        if (!$selfVettingIsAllowed && !$isSelfVetUsingSAT) {
661
            throw new DomainException(
662
                "The second factor to be vetted has a higher LoA then the Token used for proving possession",
663
            );
664
        }
665
666
        if ($isSelfVetUsingSAT) {
667
            // Assert that all previously vetted tokens are SAT tokens. If this is not the case, do not allow
668
            // self vetting using a SAT.
669
            $this->assertAllVettedTokensAreSelfAsserted();
670
            $recoveryToken = $this->recoveryTokens->first();
671
            $registeringSecondFactor->vet(true, new SelfAssertedRegistrationVettingType($recoveryToken->getTokenId()));
672
            return;
673
        }
674
        $registeringSecondFactor->vet(true, new SelfVetVettingType($authoringSecondFactorLoa));
675
    }
676
677
    /**
678
     * Copy a token from the source identity to the target identity
679
     */
680
    public function migrateVettedSecondFactor(
681
        IdentityApi $sourceIdentity,
682
        SecondFactorId $secondFactorId,
683
        string $targetSecondFactorId,
684
        int $maxNumberOfTokens,
685
    ): void {
686
        $this->assertNotForgotten();
687
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
688
        $secondFactor = $sourceIdentity->getVettedSecondFactorById($secondFactorId);
689
        if (!$secondFactor instanceof VettedSecondFactor) {
690
            throw new DomainException("The second factor on the original identity can not be found");
691
        }
692
        $this->assertTokenNotAlreadyRegistered($secondFactor->getType(), $secondFactor->getIdentifier());
693
        $this->assertTokenTypeNotAlreadyRegistered($secondFactor->getType());
694
        if ($sourceIdentity->getInstitution()->equals($this->getInstitution())) {
695
            throw new DomainException("Cannot move the second factor to the same institution");
696
        }
697
698
        $this->apply(
699
            new SecondFactorMigratedEvent(
700
                $this->getId(),
701
                $this->getNameId(),
702
                $this->getInstitution(),
703
                $sourceIdentity->getInstitution(),
704
                $secondFactorId,
705
                new SecondFactorId($targetSecondFactorId),
706
                $secondFactor->getType(),
707
                $secondFactor->getIdentifier(),
708
                $secondFactor->vettingType(),
709
                $this->getCommonName(),
710
                $this->getEmail(),
711
                $this->getPreferredLocale(),
712
            ),
713
        );
714
715
        $this->apply(
716
            new SecondFactorMigratedToEvent(
717
                $sourceIdentity->getId(),
718
                $sourceIdentity->getInstitution(),
719
                $this->getInstitution(),
720
                $secondFactor->getId(),
0 ignored issues
show
Bug introduced by
It seems like $secondFactor->getId() can also be of type null; however, parameter $secondFactorId of Surfnet\Stepup\Identity\...dToEvent::__construct() does only seem to accept Surfnet\Stepup\Identity\Value\SecondFactorId, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

720
                /** @scrutinizer ignore-type */ $secondFactor->getId(),
Loading history...
721
                new SecondFactorId($targetSecondFactorId),
722
                $secondFactor->getType(),
723
                $secondFactor->getIdentifier(),
724
            ),
725
        );
726
    }
727
728
    public function complyWithVettingOfSecondFactor(
729
        SecondFactorId         $secondFactorId,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorId"; 9 found
Loading history...
730
        SecondFactorType       $secondFactorType,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$secondFactorType"; 7 found
Loading history...
731
        SecondFactorIdentifier $secondFactorIdentifier,
732
        string                 $registrationCode,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$registrationCode"; 17 found
Loading history...
733
        DocumentNumber         $documentNumber,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$documentNumber"; 9 found
Loading history...
734
        bool                   $provePossessionSkipped,
0 ignored issues
show
Coding Style introduced by
Expected 1 space between type hint and argument "$provePossessionSkipped"; 19 found
Loading history...
735
    ): void {
736
        $this->assertNotForgotten();
737
738
        $secondFactorToVet = null;
739
        foreach ($this->verifiedSecondFactors as $secondFactor) {
740
            /** @var VerifiedSecondFactor $secondFactor */
741
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
742
                $secondFactorToVet = $secondFactor;
743
            }
744
        }
745
746
        if (!$secondFactorToVet) {
747
            throw new DomainException(
748
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
749
                'and second factor identifier',
750
            );
751
        }
752
753
        if (!$secondFactorToVet->canBeVettedNow()) {
754
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
755
        }
756
757
        $secondFactorToVet->vet($provePossessionSkipped, new OnPremiseVettingType($documentNumber));
758
    }
759
760
    public function revokeSecondFactor(SecondFactorId $secondFactorId): void
761
    {
762
        $this->assertNotForgotten();
763
764
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
765
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

765
        /** @scrutinizer ignore-call */ 
766
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
766
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
767
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

767
        /** @scrutinizer ignore-call */ 
768
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
768
        /** @var VettedSecondFactor|null $vettedSecondFactor */
769
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
770
771
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
772
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
773
        }
774
775
        if ($unverifiedSecondFactor) {
776
            $unverifiedSecondFactor->revoke();
777
778
            return;
779
        }
780
781
        if ($verifiedSecondFactor) {
782
            $verifiedSecondFactor->revoke();
783
784
            return;
785
        }
786
787
        $vettedSecondFactor->revoke();
788
789
        if ($this->vettedSecondFactors->isEmpty()) {
790
            $this->allVettedSecondFactorsRemoved();
791
        }
792
    }
793
794
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId): void
795
    {
796
        $this->assertNotForgotten();
797
798
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
799
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
800
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
801
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
802
        /** @var VettedSecondFactor|null $vettedSecondFactor */
803
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
804
805
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
806
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
807
        }
808
809
        if ($unverifiedSecondFactor) {
810
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
811
812
            return;
813
        }
814
815
        if ($verifiedSecondFactor) {
816
            $verifiedSecondFactor->complyWithRevocation($authorityId);
817
818
            return;
819
        }
820
821
        $vettedSecondFactor->complyWithRevocation($authorityId);
822
823
        if ($this->vettedSecondFactors->isEmpty()) {
824
            $this->allVettedSecondFactorsRemoved();
825
        }
826
    }
827
828
    public function revokeRecoveryToken(RecoveryTokenId $recoveryTokenId): void
829
    {
830
        $this->assertNotForgotten();
831
        try {
832
            $recoveryToken = $this->recoveryTokens->get($recoveryTokenId);
833
        } catch (DomainException $e) {
834
            throw new DomainException('Cannot revoke recovery token: no token with given id exists.', 0, $e);
835
        }
836
        $recoveryToken->revoke();
837
    }
838
839
    public function complyWithRecoveryTokenRevocation(RecoveryTokenId $recoveryTokenId, IdentityId $authorityId): void
840
    {
841
        $this->assertNotForgotten();
842
        try {
843
            $recoveryToken = $this->recoveryTokens->get($recoveryTokenId);
844
        } catch (DomainException $e) {
845
            throw new DomainException('Cannot revoke recovery token: no token with given id exists.', 0, $e);
846
        }
847
        $recoveryToken->complyWithRevocation($authorityId);
848
    }
849
850
    /**
851
     * @param RegistrationAuthorityRole $role
852
     * @param Institution $institution
853
     * @param Location $location
854
     * @param ContactInformation $contactInformation
855
     * @param InstitutionConfiguration $institutionConfiguration
856
     * @return void
857
     */
858
    public function accreditWith(
859
        RegistrationAuthorityRole $role,
860
        Institution $institution,
861
        Location $location,
862
        ContactInformation $contactInformation,
863
        InstitutionConfiguration $institutionConfiguration,
864
    ): void {
865
        $this->assertNotForgotten();
866
867
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
868
            new ConfigurationInstitution($this->institution->getInstitution()),
869
        )) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line control structure must be indented at least once; expected at least 12 spaces, but found 8
Loading history...
Coding Style introduced by
The closing parenthesis of a multi-line control structure must be on the line after the last expression
Loading history...
870
            throw new DomainException('An Identity may only be accredited by configured institutions.');
871
        }
872
873
        if (!$this->vettedSecondFactors->count()) {
874
            throw new DomainException(
875
                'An Identity must have at least one vetted second factor before it can be accredited',
876
            );
877
        }
878
879
        if ($this->registrationAuthorities->exists($institution)) {
0 ignored issues
show
Bug introduced by
The method exists() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

879
        if ($this->registrationAuthorities->/** @scrutinizer ignore-call */ exists($institution)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
880
            throw new DomainException('Cannot accredit Identity as it has already been accredited for institution');
881
        }
882
883
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
884
            $this->apply(
885
                new IdentityAccreditedAsRaForInstitutionEvent(
886
                    $this->id,
887
                    $this->nameId,
888
                    $this->institution,
889
                    $role,
890
                    $location,
891
                    $contactInformation,
892
                    $institution,
893
                ),
894
            );
895
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
896
            $this->apply(
897
                new IdentityAccreditedAsRaaForInstitutionEvent(
898
                    $this->id,
899
                    $this->nameId,
900
                    $this->institution,
901
                    $role,
902
                    $location,
903
                    $contactInformation,
904
                    $institution,
905
                ),
906
            );
907
        } else {
908
            throw new DomainException('An Identity can only be accredited with either the RA or RAA role');
909
        }
910
    }
911
912
    public function amendRegistrationAuthorityInformation(
913
        Institution $institution,
914
        Location $location,
915
        ContactInformation $contactInformation,
916
    ): void {
917
        $this->assertNotForgotten();
918
919
        if (!$this->registrationAuthorities->exists($institution)) {
920
            throw new DomainException(
921
                'Cannot amend registration authority information: identity is not a registration authority for institution',
922
            );
923
        }
924
925
        $this->apply(
926
            new RegistrationAuthorityInformationAmendedForInstitutionEvent(
927
                $this->id,
928
                $this->institution,
929
                $this->nameId,
930
                $location,
931
                $contactInformation,
932
                $institution,
933
            ),
934
        );
935
    }
936
937
    /**
938
     * This method will appoint an institution to become ra or raa for another institution
939
     *
940
     * @param Institution $institution
941
     * @param RegistrationAuthorityRole $role
942
     * @param InstitutionConfiguration $institutionConfiguration
943
     */
944
    public function appointAs(
945
        Institution $institution,
946
        RegistrationAuthorityRole $role,
947
        InstitutionConfiguration $institutionConfiguration,
948
    ): void {
949
        $this->assertNotForgotten();
950
951
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
952
            new ConfigurationInstitution($this->institution->getInstitution()),
953
        )) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line control structure must be indented at least once; expected at least 12 spaces, but found 8
Loading history...
Coding Style introduced by
The closing parenthesis of a multi-line control structure must be on the line after the last expression
Loading history...
954
            throw new DomainException(
955
                'Cannot appoint as different RegistrationAuthorityRole: identity is not a registration authority for institution',
956
            );
957
        }
958
959
        $registrationAuthority = $this->registrationAuthorities->get($institution);
960
961
        if ($registrationAuthority->isAppointedAs($role)) {
962
            return;
963
        }
964
965
        if ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA))) {
966
            $this->apply(
967
                new AppointedAsRaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution),
968
            );
969
        } elseif ($role->equals(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA))) {
970
            $this->apply(
971
                new AppointedAsRaaForInstitutionEvent($this->id, $this->institution, $this->nameId, $institution),
972
            );
973
        } else {
974
            throw new DomainException('An Identity can only be appointed as either RA or RAA');
975
        }
976
    }
977
978
    public function retractRegistrationAuthority(Institution $institution): void
979
    {
980
        $this->assertNotForgotten();
981
982
        if (!$this->registrationAuthorities->exists($institution)) {
983
            throw new DomainException(
984
                'Cannot Retract Registration Authority as the Identity is not a registration authority',
985
            );
986
        }
987
988
        $this->apply(
989
            new RegistrationAuthorityRetractedForInstitutionEvent(
990
                $this->id,
991
                $this->institution,
992
                $this->nameId,
993
                $this->commonName,
994
                $this->email,
995
                $institution,
996
            ),
997
        );
998
    }
999
1000
    public function expressPreferredLocale(Locale $preferredLocale): void
1001
    {
1002
        $this->assertNotForgotten();
1003
1004
        if ($this->preferredLocale === $preferredLocale) {
1005
            return;
1006
        }
1007
1008
        $this->apply(new LocalePreferenceExpressedEvent($this->id, $this->institution, $preferredLocale));
1009
    }
1010
1011
    public function forget(): void
1012
    {
1013
        if ($this->registrationAuthorities->count() !== 0) {
1014
            throw new DomainException('Cannot forget an identity that is currently accredited as an RA(A)');
1015
        }
1016
1017
        $this->apply(new IdentityForgottenEvent($this->id, $this->institution));
1018
    }
1019
1020
    public function restore(
1021
        CommonName $commonName,
1022
        Email $email,
1023
    ): void {
1024
        if (!$this->forgotten) {
1025
            return;
1026
        }
1027
1028
        $this->apply(new IdentityRestoredEvent($this->id, $this->institution, $commonName, $email));
1029
    }
1030
1031
    public function allVettedSecondFactorsRemoved(): void
1032
    {
1033
        $this->apply(
1034
            new VettedSecondFactorsAllRevokedEvent(
1035
                $this->id,
1036
                $this->institution,
1037
            ),
1038
        );
1039
    }
1040
1041
    protected function applyIdentityCreatedEvent(IdentityCreatedEvent $event): void
1042
    {
1043
        $this->id = $event->identityId;
1044
        $this->institution = $event->identityInstitution;
1045
        $this->nameId = $event->nameId;
1046
        $this->commonName = $event->commonName;
1047
        $this->email = $event->email;
1048
        $this->preferredLocale = $event->preferredLocale;
1049
        $this->forgotten = false;
1050
1051
        $this->unverifiedSecondFactors = new SecondFactorCollection();
1052
        $this->verifiedSecondFactors = new SecondFactorCollection();
1053
        $this->vettedSecondFactors = new SecondFactorCollection();
1054
        $this->registrationAuthorities = new RegistrationAuthorityCollection();
1055
        $this->recoveryTokens = new RecoveryTokenCollection();
1056
    }
1057
1058
    protected function applyIdentityRestoredEvent(IdentityRestoredEvent $event): void
1059
    {
1060
        $this->unverifiedSecondFactors = new SecondFactorCollection();
1061
        $this->verifiedSecondFactors = new SecondFactorCollection();
1062
        $this->vettedSecondFactors = new SecondFactorCollection();
1063
        $this->registrationAuthorities = new RegistrationAuthorityCollection();
1064
        $this->recoveryTokens = new RecoveryTokenCollection();
1065
1066
        $this->commonName = $event->commonName;
1067
        $this->email = $event->email;
1068
        $this->forgotten = false;
1069
    }
1070
1071
    public function applyIdentityRenamedEvent(IdentityRenamedEvent $event): void
1072
    {
1073
        $this->commonName = $event->commonName;
1074
    }
1075
1076
    public function applyIdentityEmailChangedEvent(IdentityEmailChangedEvent $event): void
1077
    {
1078
        $this->email = $event->email;
1079
    }
1080
1081
    protected function applyYubikeySecondFactorBootstrappedEvent(YubikeySecondFactorBootstrappedEvent $event): void
1082
    {
1083
        $secondFactor = VettedSecondFactor::create(
1084
            $event->secondFactorId,
1085
            $this,
1086
            new SecondFactorType('yubikey'),
1087
            $event->yubikeyPublicId,
1088
            new UnknownVettingType(),
1089
        );
1090
1091
        $this->vettedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1092
    }
1093
1094
    protected function applyYubikeyPossessionProvenEvent(YubikeyPossessionProvenEvent $event): void
1095
    {
1096
        $secondFactor = UnverifiedSecondFactor::create(
1097
            $event->secondFactorId,
1098
            $this,
1099
            new SecondFactorType('yubikey'),
1100
            $event->yubikeyPublicId,
1101
            $event->emailVerificationWindow,
1102
            $event->emailVerificationNonce,
1103
        );
1104
1105
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1106
    }
1107
1108
    protected function applyYubikeyPossessionProvenAndVerifiedEvent(YubikeyPossessionProvenAndVerifiedEvent $event): void
1109
    {
1110
        $secondFactor = VerifiedSecondFactor::create(
1111
            $event->secondFactorId,
1112
            $this,
1113
            new SecondFactorType('yubikey'),
1114
            $event->yubikeyPublicId,
1115
            $event->registrationRequestedAt,
1116
            $event->registrationCode,
1117
        );
1118
1119
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1120
    }
1121
1122
    protected function applyPhonePossessionProvenEvent(PhonePossessionProvenEvent $event): void
1123
    {
1124
        $secondFactor = UnverifiedSecondFactor::create(
1125
            $event->secondFactorId,
1126
            $this,
1127
            new SecondFactorType('sms'),
1128
            $event->phoneNumber,
1129
            $event->emailVerificationWindow,
1130
            $event->emailVerificationNonce,
1131
        );
1132
1133
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1134
    }
1135
1136
    protected function applyPhonePossessionProvenAndVerifiedEvent(PhonePossessionProvenAndVerifiedEvent $event): void
1137
    {
1138
        $secondFactor = VerifiedSecondFactor::create(
1139
            $event->secondFactorId,
1140
            $this,
1141
            new SecondFactorType('sms'),
1142
            $event->phoneNumber,
1143
            $event->registrationRequestedAt,
1144
            $event->registrationCode,
1145
        );
1146
1147
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1148
    }
1149
1150
    protected function applyGssfPossessionProvenEvent(GssfPossessionProvenEvent $event): void
1151
    {
1152
        $secondFactor = UnverifiedSecondFactor::create(
1153
            $event->secondFactorId,
1154
            $this,
1155
            new SecondFactorType((string)$event->stepupProvider),
1156
            $event->gssfId,
1157
            $event->emailVerificationWindow,
1158
            $event->emailVerificationNonce,
1159
        );
1160
1161
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1162
    }
1163
1164
    protected function applyGssfPossessionProvenAndVerifiedEvent(GssfPossessionProvenAndVerifiedEvent $event): void
1165
    {
1166
        $secondFactor = VerifiedSecondFactor::create(
1167
            $event->secondFactorId,
1168
            $this,
1169
            new SecondFactorType((string)$event->stepupProvider),
1170
            $event->gssfId,
1171
            $event->registrationRequestedAt,
1172
            $event->registrationCode,
1173
        );
1174
1175
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1176
    }
1177
1178
    protected function applyU2fDevicePossessionProvenEvent(U2fDevicePossessionProvenEvent $event): void
1179
    {
1180
        $secondFactor = UnverifiedSecondFactor::create(
1181
            $event->secondFactorId,
1182
            $this,
1183
            new SecondFactorType('u2f'),
1184
            $event->keyHandle,
1185
            $event->emailVerificationWindow,
1186
            $event->emailVerificationNonce,
1187
        );
1188
1189
        $this->unverifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1190
    }
1191
1192
    protected function applyU2fDevicePossessionProvenAndVerifiedEvent(U2fDevicePossessionProvenAndVerifiedEvent $event): void
1193
    {
1194
        $secondFactor = VerifiedSecondFactor::create(
1195
            $event->secondFactorId,
1196
            $this,
1197
            new SecondFactorType('u2f'),
1198
            $event->keyHandle,
1199
            $event->registrationRequestedAt,
1200
            $event->registrationCode,
1201
        );
1202
1203
        $this->verifiedSecondFactors->set((string)$secondFactor->getId(), $secondFactor);
1204
    }
1205
1206
    protected function applyPhoneRecoveryTokenPossessionProvenEvent(PhoneRecoveryTokenPossessionProvenEvent $event): void
1207
    {
1208
        $recoveryToken = RecoveryTokenEntity::create($event->recoveryTokenId, RecoveryTokenType::sms(), $this);
1209
1210
        $this->recoveryTokens->set($recoveryToken);
1211
    }
1212
1213
    protected function applySafeStoreSecretRecoveryTokenPossessionPromisedEvent(
1214
        SafeStoreSecretRecoveryTokenPossessionPromisedEvent $event,
1215
    ): void {
1216
        $recoveryToken = RecoveryTokenEntity::create($event->recoveryTokenId, RecoveryTokenType::safeStore(), $this);
1217
1218
        $this->recoveryTokens->set($recoveryToken);
1219
    }
1220
1221
    protected function applyEmailVerifiedEvent(EmailVerifiedEvent $event): void
1222
    {
1223
        $secondFactorId = (string)$event->secondFactorId;
1224
1225
        /** @var UnverifiedSecondFactor $unverified */
1226
        $unverified = $this->unverifiedSecondFactors->get($secondFactorId);
1227
        $verified = $unverified->asVerified($event->registrationRequestedAt, $event->registrationCode);
1228
1229
        $this->unverifiedSecondFactors->remove($secondFactorId);
1230
        $this->verifiedSecondFactors->set($secondFactorId, $verified);
1231
    }
1232
1233
    /**
1234
     * The SecondFactorMigratedToEvent is applied by creating a new
1235
     * vetted second factor on the target identity. The source
1236
     * second factor is not yet forgotten.
1237
     */
1238
    public function applySecondFactorMigratedEvent(SecondFactorMigratedEvent $event): void
1239
    {
1240
        $secondFactorId = (string)$event->newSecondFactorId;
1241
        $vetted = VettedSecondFactor::create(
1242
            $event->newSecondFactorId,
1243
            $this,
1244
            $event->secondFactorType,
1245
            $event->secondFactorIdentifier,
1246
            $event->vettingType,
1247
        );
1248
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
1249
    }
1250
1251
    protected function applySecondFactorVettedEvent(SecondFactorVettedEvent $event): void
1252
    {
1253
        $secondFactorId = (string)$event->secondFactorId;
1254
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
1255
        $vetted = $verified->asVetted($event->vettingType);
1256
        $this->verifiedSecondFactors->remove($secondFactorId);
1257
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
1258
    }
1259
1260
    protected function applySecondFactorVettedWithoutTokenProofOfPossession(
1261
        SecondFactorVettedWithoutTokenProofOfPossession $event,
1262
    ): void {
1263
        $secondFactorId = (string)$event->secondFactorId;
1264
1265
        /** @var VerifiedSecondFactor $verified */
1266
        $verified = $this->verifiedSecondFactors->get($secondFactorId);
1267
        $vetted = $verified->asVetted($event->vettingType);
0 ignored issues
show
Bug introduced by
It seems like $event->vettingType can also be of type null; however, parameter $vettingType of Surfnet\Stepup\Identity\...econdFactor::asVetted() does only seem to accept Surfnet\Stepup\Identity\Value\VettingType, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1267
        $vetted = $verified->asVetted(/** @scrutinizer ignore-type */ $event->vettingType);
Loading history...
1268
1269
        $this->verifiedSecondFactors->remove($secondFactorId);
1270
        $this->vettedSecondFactors->set($secondFactorId, $vetted);
1271
    }
1272
1273
    protected function applyUnverifiedSecondFactorRevokedEvent(UnverifiedSecondFactorRevokedEvent $event): void
1274
    {
1275
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
1276
    }
1277
1278
    protected function applyCompliedWithUnverifiedSecondFactorRevocationEvent(
1279
        CompliedWithUnverifiedSecondFactorRevocationEvent $event,
1280
    ): void {
1281
        $this->unverifiedSecondFactors->remove((string)$event->secondFactorId);
1282
    }
1283
1284
    protected function applyVerifiedSecondFactorRevokedEvent(VerifiedSecondFactorRevokedEvent $event): void
1285
    {
1286
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
1287
    }
1288
1289
    protected function applyCompliedWithVerifiedSecondFactorRevocationEvent(
1290
        CompliedWithVerifiedSecondFactorRevocationEvent $event,
1291
    ): void {
1292
        $this->verifiedSecondFactors->remove((string)$event->secondFactorId);
1293
    }
1294
1295
    protected function applyVettedSecondFactorRevokedEvent(VettedSecondFactorRevokedEvent $event): void
1296
    {
1297
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
1298
    }
1299
1300
    protected function applyCompliedWithVettedSecondFactorRevocationEvent(
1301
        CompliedWithVettedSecondFactorRevocationEvent $event,
1302
    ): void {
1303
        $this->vettedSecondFactors->remove((string)$event->secondFactorId);
1304
    }
1305
1306
    protected function applyCompliedWithRecoveryCodeRevocationEvent(CompliedWithRecoveryCodeRevocationEvent $event): void
1307
    {
1308
        $this->recoveryTokens->remove($event->recoveryTokenId);
1309
    }
1310
1311
    protected function applyRecoveryTokenRevokedEvent(RecoveryTokenRevokedEvent $event): void
1312
    {
1313
        $this->recoveryTokens->remove($event->recoveryTokenId);
1314
    }
1315
1316
    protected function applyIdentityAccreditedAsRaForInstitutionEvent(IdentityAccreditedAsRaForInstitutionEvent $event): void
1317
    {
1318
        $this->registrationAuthorities->set(
1319
            $event->raInstitution,
1320
            RegistrationAuthority::accreditWith(
1321
                $event->registrationAuthorityRole,
1322
                $event->location,
1323
                $event->contactInformation,
1324
                $event->raInstitution,
1325
            ),
1326
        );
1327
    }
1328
1329
    protected function applyIdentityAccreditedAsRaaForInstitutionEvent(IdentityAccreditedAsRaaForInstitutionEvent $event,): void
1330
    {
1331
        $this->registrationAuthorities->set(
1332
            $event->raInstitution,
1333
            RegistrationAuthority::accreditWith(
1334
                $event->registrationAuthorityRole,
1335
                $event->location,
1336
                $event->contactInformation,
1337
                $event->raInstitution,
1338
            ),
1339
        );
1340
    }
1341
1342
    protected function applyRegistrationAuthorityInformationAmendedForInstitutionEvent(
1343
        RegistrationAuthorityInformationAmendedForInstitutionEvent $event,
1344
    ): void {
1345
        $this->registrationAuthorities->get($event->raInstitution)->amendInformation(
1346
            $event->location,
1347
            $event->contactInformation,
1348
        );
1349
    }
1350
1351
    protected function applyAppointedAsRaaForInstitutionEvent(AppointedAsRaaForInstitutionEvent $event): void
1352
    {
1353
        $this->registrationAuthorities->get($event->raInstitution)->appointAs(
1354
            new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA),
1355
        );
1356
    }
1357
1358
    protected function applyRegistrationAuthorityRetractedForInstitutionEvent(
1359
        RegistrationAuthorityRetractedForInstitutionEvent $event,
1360
    ): void {
1361
        $this->registrationAuthorities->remove($event->raInstitution);
1362
    }
1363
1364
    protected function applyLocalePreferenceExpressedEvent(LocalePreferenceExpressedEvent $event): void
1365
    {
1366
        $this->preferredLocale = $event->preferredLocale;
1367
    }
1368
1369
    protected function applyIdentityForgottenEvent(IdentityForgottenEvent $event): void
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1369
    protected function applyIdentityForgottenEvent(/** @scrutinizer ignore-unused */ IdentityForgottenEvent $event): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1370
    {
1371
        $this->commonName = CommonName::unknown();
1372
        $this->email = Email::unknown();
1373
        $this->forgotten = true;
1374
    }
1375
1376
    /**
1377
     * This method is kept to be backwards compatible for changes before FGA
1378
     */
1379
    protected function applyAppointedAsRaEvent(AppointedAsRaEvent $event): void
1380
    {
1381
        $this->registrationAuthorities->get($event->identityInstitution)
1382
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1383
    }
1384
1385
    /**
1386
     * This method is kept to be backwards compatible for changes before FGA
1387
     */
1388
    protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $event): void
1389
    {
1390
        $this->registrationAuthorities->get($event->identityInstitution)
1391
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1392
    }
1393
1394
    /**
1395
     * This method is kept to be backwards compatible for changes before FGA
1396
     */
1397
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event): void
1398
    {
1399
        $this->registrationAuthorities->set(
1400
            $event->identityInstitution,
1401
            RegistrationAuthority::accreditWith(
1402
                $event->registrationAuthorityRole,
1403
                $event->location,
1404
                $event->contactInformation,
1405
                $event->identityInstitution,
1406
            ),
1407
        );
1408
    }
1409
1410
    /**
1411
     * This method is kept to be backwards compatible for changes before FGA
1412
     */
1413
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event): void
1414
    {
1415
        $this->registrationAuthorities->set(
1416
            $event->identityInstitution,
1417
            RegistrationAuthority::accreditWith(
1418
                $event->registrationAuthorityRole,
1419
                $event->location,
1420
                $event->contactInformation,
1421
                $event->identityInstitution,
1422
            ),
1423
        );
1424
    }
1425
1426
    /**
1427
     * This method is kept to be backwards compatible for changes before FGA
1428
     */
1429
    protected function applyAppointedAsRaForInstitutionEvent(AppointedAsRaForInstitutionEvent $event): void
1430
    {
1431
        $this->registrationAuthorities->get($event->identityInstitution)
1432
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1433
    }
1434
1435
    /**
1436
     * This method is kept to be backwards compatible for changes before FGA
1437
     */
1438
    protected function applyRegistrationAuthorityInformationAmendedEvent(
1439
        RegistrationAuthorityInformationAmendedEvent $event,
1440
    ): void {
1441
        $this->registrationAuthorities->get($event->identityInstitution)->amendInformation(
1442
            $event->location,
1443
            $event->contactInformation,
1444
        );
1445
    }
1446
1447
    /**
1448
     * This method is kept to be backwards compatible for changes before FGA
1449
     */
1450
    protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $event): void
1451
    {
1452
        $this->registrationAuthorities->remove($event->identityInstitution);
1453
    }
1454
1455
1456
    public function getAggregateRootId(): string
1457
    {
1458
        return $this->id->getIdentityId();
1459
    }
1460
1461
    protected function getChildEntities(): array
1462
    {
1463
        return array_merge(
1464
            $this->unverifiedSecondFactors->getValues(),
1465
            $this->verifiedSecondFactors->getValues(),
1466
            $this->vettedSecondFactors->getValues(),
1467
            $this->registrationAuthorities->getValues(),
1468
        );
1469
    }
1470
1471
    /**
1472
     * @throws DomainException
1473
     */
1474
    private function assertNotForgotten(): void
1475
    {
1476
        if ($this->forgotten) {
1477
            throw new DomainException('Operation on this Identity is not allowed: it has been forgotten');
1478
        }
1479
    }
1480
1481
    /**
1482
     * @throws DomainException
1483
     */
1484
    private function assertUserMayAddSecondFactor(int $maxNumberOfTokens): void
1485
    {
1486
        $tokenCount = (int) count($this->unverifiedSecondFactors) + count($this->verifiedSecondFactors) + count($this->vettedSecondFactors);
0 ignored issues
show
Bug introduced by
It seems like $this->unverifiedSecondFactors can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1486
        $tokenCount = (int) count(/** @scrutinizer ignore-type */ $this->unverifiedSecondFactors) + count($this->verifiedSecondFactors) + count($this->vettedSecondFactors);
Loading history...
1487
        if ($tokenCount >= $maxNumberOfTokens
0 ignored issues
show
Coding Style introduced by
The first expression of a multi-line control structure must be on the line after the opening parenthesis
Loading history...
1488
        ) {
1489
            throw new DomainException(
1490
                sprintf('User may not have more than %d token(s)', $maxNumberOfTokens),
1491
            );
1492
        }
1493
    }
1494
1495
    private function assertUserMayAddRecoveryToken(RecoveryTokenType $recoveryTokenType): void
1496
    {
1497
        // Assert this token type is not yet registered
1498
        if ($this->recoveryTokens->hasType($recoveryTokenType)) {
1499
            throw new DomainException(
1500
                sprintf('Recovery token type %s is already registered', (string)$recoveryTokenType),
1501
            );
1502
        }
1503
    }
1504
1505
    public function getId(): IdentityId
1506
    {
1507
        return $this->id;
1508
    }
1509
1510
    /**
1511
     * @return NameId
1512
     */
1513
    public function getNameId(): NameId
1514
    {
1515
        return $this->nameId;
1516
    }
1517
1518
    /**
1519
     * @return Institution
1520
     */
1521
    public function getInstitution(): Institution
1522
    {
1523
        return $this->institution;
1524
    }
1525
1526
    public function getCommonName(): CommonName
1527
    {
1528
        return $this->commonName;
1529
    }
1530
1531
    public function getEmail(): Email
1532
    {
1533
        return $this->email;
1534
    }
1535
1536
    public function getPreferredLocale(): Locale
1537
    {
1538
        return $this->preferredLocale;
1539
    }
1540
1541
    public function getVerifiedSecondFactor(SecondFactorId $secondFactorId): ?VerifiedSecondFactor
1542
    {
1543
        return $this->verifiedSecondFactors->get((string)$secondFactorId);
1544
    }
1545
1546
    public function getVettedSecondFactorById(SecondFactorId $secondFactorId): ?VettedSecondFactor
1547
    {
1548
        return $this->vettedSecondFactors->get((string)$secondFactorId);
1549
    }
1550
1551
    private function assertTokenNotAlreadyRegistered(SecondFactorType $type, SecondFactorIdentifier $identifier): void
1552
    {
1553
        foreach ($this->unverifiedSecondFactors as $unverified) {
1554
            if ($unverified->typeAndIdentifierAreEqual($type, $identifier)) {
1555
                throw new DomainException("The second factor was already registered as an unverified second factor");
1556
            }
1557
        }
1558
        foreach ($this->verifiedSecondFactors as $verified) {
1559
            if ($verified->typeAndIdentifierAreEqual($type, $identifier)) {
1560
                throw new DomainException("The second factor was already registered as a verified second factor");
1561
            }
1562
        }
1563
        foreach ($this->vettedSecondFactors as $vetted) {
1564
            if ($vetted->typeAndIdentifierAreEqual($type, $identifier)) {
1565
                throw new DomainException("The second factor was registered as a vetted second factor");
1566
            }
1567
        }
1568
    }
1569
1570
    private function assertTokenTypeNotAlreadyRegistered(SecondFactorType $type): void
1571
    {
1572
        foreach ($this->unverifiedSecondFactors as $unverified) {
1573
            if ($unverified->getType()->equals($type)) {
1574
                throw new DomainException("This second factor type was already registered as an unverified second factor");
1575
            }
1576
        }
1577
        foreach ($this->verifiedSecondFactors as $verified) {
1578
            if ($verified->getType()->equals($type)) {
1579
                throw new DomainException("This second factor type was already registered as a verified second factor");
1580
            }
1581
        }
1582
        foreach ($this->vettedSecondFactors as $vetted) {
1583
            if ($vetted->getType()->equals($type)) {
1584
                throw new DomainException("This second factor type was already registered as a vetted second factor");
1585
            }
1586
        }
1587
    }
1588
1589
    private function assertSelfAssertedTokenRegistrationAllowed(): void
1590
    {
1591
        if ($this->vettedSecondFactors->count() !== 0) {
1592
            throw new DomainException(
1593
                "Self-asserted second factor registration is only allowed when no tokens are vetted yet",
1594
            );
1595
        }
1596
        if ($this->recoveryTokens->count() === 0) {
1597
            throw new DomainException("A recovery token is required to perform a self-asserted token registration");
1598
        }
1599
    }
1600
1601
    /**
1602
     * Verify that every vetted second factor is self-asserted
1603
     */
1604
    private function assertAllVettedTokensAreSelfAsserted(): void
1605
    {
1606
        /** @var VettedSecondFactor $vettedSecondFactor */
1607
        foreach ($this->vettedSecondFactors as $vettedSecondFactor) {
1608
            if ($vettedSecondFactor->vettingType()->type() !== VettingType::TYPE_SELF_ASSERTED_REGISTRATION) {
1609
                throw new DomainException(
1610
                    'Not all tokens are self-asserted, it is not allowed to self-vet using the self-asserted token',
1611
                );
1612
            }
1613
        }
1614
    }
1615
}
1616