Passed
Pull Request — main (#526)
by
unknown
11:42 queued 05:57
created

Identity::applyIdentityRestoredEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 1
dl 0
loc 11
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
 */
0 ignored issues
show
Coding Style introduced by
Missing @link tag in file comment
Loading history...
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\Exception\IdentityForgottenException;
27
use Surfnet\Stepup\Helper\SecondFactorProvePossessionHelper;
28
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
29
use Surfnet\Stepup\Identity\Collection\VettingTypeHintCollection;
30
use Surfnet\Stepup\Identity\Entity\RecoveryToken as RecoveryTokenEntity;
31
use Surfnet\Stepup\Identity\Entity\RecoveryTokenCollection;
32
use Surfnet\Stepup\Identity\Entity\RegistrationAuthority;
33
use Surfnet\Stepup\Identity\Entity\RegistrationAuthorityCollection;
34
use Surfnet\Stepup\Identity\Entity\SecondFactorCollection;
35
use Surfnet\Stepup\Identity\Entity\UnverifiedSecondFactor;
36
use Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor;
37
use Surfnet\Stepup\Identity\Entity\VettedSecondFactor;
38
use Surfnet\Stepup\Identity\Event\AppointedAsRaaEvent;
39
use Surfnet\Stepup\Identity\Event\AppointedAsRaaForInstitutionEvent;
40
use Surfnet\Stepup\Identity\Event\AppointedAsRaEvent;
41
use Surfnet\Stepup\Identity\Event\AppointedAsRaForInstitutionEvent;
42
use Surfnet\Stepup\Identity\Event\CompliedWithRecoveryCodeRevocationEvent;
43
use Surfnet\Stepup\Identity\Event\CompliedWithUnverifiedSecondFactorRevocationEvent;
44
use Surfnet\Stepup\Identity\Event\CompliedWithVerifiedSecondFactorRevocationEvent;
45
use Surfnet\Stepup\Identity\Event\CompliedWithVettedSecondFactorRevocationEvent;
46
use Surfnet\Stepup\Identity\Event\EmailVerifiedEvent;
47
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenAndVerifiedEvent;
48
use Surfnet\Stepup\Identity\Event\GssfPossessionProvenEvent;
49
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaEvent;
50
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaaForInstitutionEvent;
51
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaEvent;
52
use Surfnet\Stepup\Identity\Event\IdentityAccreditedAsRaForInstitutionEvent;
53
use Surfnet\Stepup\Identity\Event\IdentityCreatedEvent;
54
use Surfnet\Stepup\Identity\Event\IdentityEmailChangedEvent;
55
use Surfnet\Stepup\Identity\Event\IdentityForgottenEvent;
56
use Surfnet\Stepup\Identity\Event\IdentityRestoredEvent;
57
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
58
use Surfnet\Stepup\Identity\Event\LocalePreferenceExpressedEvent;
59
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenAndVerifiedEvent;
60
use Surfnet\Stepup\Identity\Event\PhonePossessionProvenEvent;
61
use Surfnet\Stepup\Identity\Event\PhoneRecoveryTokenPossessionProvenEvent;
62
use Surfnet\Stepup\Identity\Event\RecoveryTokenRevokedEvent;
63
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedEvent;
64
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityInformationAmendedForInstitutionEvent;
65
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedEvent;
66
use Surfnet\Stepup\Identity\Event\RegistrationAuthorityRetractedForInstitutionEvent;
67
use Surfnet\Stepup\Identity\Event\SafeStoreSecretRecoveryTokenPossessionPromisedEvent;
68
use Surfnet\Stepup\Identity\Event\SecondFactorMigratedEvent;
69
use Surfnet\Stepup\Identity\Event\SecondFactorMigratedToEvent;
70
use Surfnet\Stepup\Identity\Event\SecondFactorVettedEvent;
71
use Surfnet\Stepup\Identity\Event\SecondFactorVettedWithoutTokenProofOfPossession;
72
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenAndVerifiedEvent;
73
use Surfnet\Stepup\Identity\Event\U2fDevicePossessionProvenEvent;
74
use Surfnet\Stepup\Identity\Event\UnverifiedSecondFactorRevokedEvent;
75
use Surfnet\Stepup\Identity\Event\VerifiedSecondFactorRevokedEvent;
76
use Surfnet\Stepup\Identity\Event\VettedSecondFactorRevokedEvent;
77
use Surfnet\Stepup\Identity\Event\VettedSecondFactorsAllRevokedEvent;
78
use Surfnet\Stepup\Identity\Event\VettingTypeHintsSavedEvent;
79
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenAndVerifiedEvent;
80
use Surfnet\Stepup\Identity\Event\YubikeyPossessionProvenEvent;
81
use Surfnet\Stepup\Identity\Event\YubikeySecondFactorBootstrappedEvent;
82
use Surfnet\Stepup\Identity\Value\CommonName;
83
use Surfnet\Stepup\Identity\Value\ContactInformation;
84
use Surfnet\Stepup\Identity\Value\DocumentNumber;
85
use Surfnet\Stepup\Identity\Value\Email;
86
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...
87
use Surfnet\Stepup\Identity\Value\GssfId;
88
use Surfnet\Stepup\Identity\Value\IdentityId;
89
use Surfnet\Stepup\Identity\Value\Institution;
90
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...
91
use Surfnet\Stepup\Identity\Value\Location;
92
use Surfnet\Stepup\Identity\Value\NameId;
93
use Surfnet\Stepup\Identity\Value\OnPremiseVettingType;
94
use Surfnet\Stepup\Identity\Value\PhoneNumber;
95
use Surfnet\Stepup\Identity\Value\RecoveryTokenId;
96
use Surfnet\Stepup\Identity\Value\RecoveryTokenType;
97
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...
98
use Surfnet\Stepup\Identity\Value\SafeStore;
99
use Surfnet\Stepup\Identity\Value\SecondFactorId;
100
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifier;
101
use Surfnet\Stepup\Identity\Value\SelfAssertedRegistrationVettingType;
102
use Surfnet\Stepup\Identity\Value\SelfVetVettingType;
103
use Surfnet\Stepup\Identity\Value\StepupProvider;
104
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
105
use Surfnet\Stepup\Identity\Value\UnknownVettingType;
106
use Surfnet\Stepup\Identity\Value\VettingType;
107
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
108
use Surfnet\Stepup\Token\TokenGenerator;
109
use Surfnet\StepupBundle\Security\OtpGenerator;
110
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
111
use Surfnet\StepupBundle\Value\Loa;
112
use Surfnet\StepupBundle\Value\SecondFactorType;
113
114
/**
115
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
116
 * @SuppressWarnings(PHPMD.TooManyMethods)
117
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
118
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
119
 * @SuppressWarnings(PHPMD.ExcessiveClassLength)
120
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
121
class Identity extends EventSourcedAggregateRoot implements IdentityApi
122
{
123
    /**
124
     * @var IdentityId
125
     */
126
    private IdentityId $id;
127
128
    /**
129
     * @var Institution
130
     */
131
    private Institution $institution;
132
133
    /**
134
     * @var NameId
135
     */
136
    private NameId $nameId;
137
138
    /**
139
     * @var CommonName
140
     */
141
    private CommonName $commonName;
142
143
    /**
144
     * @var Email
145
     */
146
    private Email $email;
147
148
    private ?SecondFactorCollection $unverifiedSecondFactors = null;
149
150
    private ?SecondFactorCollection $verifiedSecondFactors = null;
151
152
    private ?SecondFactorCollection $vettedSecondFactors = null;
153
154
    private ?RegistrationAuthorityCollection $registrationAuthorities = null;
155
156
    /**
157
     * @var Locale
158
     */
159
    private Locale $preferredLocale;
160
161
    private ?bool $forgotten = null;
162
163
    private ?RecoveryTokenCollection $recoveryTokens = null;
164
165
    public static function create(
166
        IdentityId $id,
167
        Institution $institution,
168
        NameId $nameId,
169
        CommonName $commonName,
170
        Email $email,
171
        Locale $preferredLocale,
172
    ): self {
173
        $identity = new self();
174
        $identity->apply(new IdentityCreatedEvent($id, $institution, $nameId, $commonName, $email, $preferredLocale));
175
176
        return $identity;
177
    }
178
179
    final public function __construct()
180
    {
181
    }
182
183
    public function rename(CommonName $commonName): void
184
    {
185
        $this->assertNotForgotten();
186
187
        if ($this->commonName->equals($commonName)) {
188
            return;
189
        }
190
191
        $this->commonName = $commonName;
192
        $this->apply(new IdentityRenamedEvent($this->id, $this->institution, $commonName));
193
    }
194
195
    public function changeEmail(Email $email): void
196
    {
197
        $this->assertNotForgotten();
198
199
        if ($this->email->equals($email)) {
200
            return;
201
        }
202
203
        $this->email = $email;
204
        $this->apply(new IdentityEmailChangedEvent($this->id, $this->institution, $email));
205
    }
206
207
    public function bootstrapYubikeySecondFactor(
208
        SecondFactorId  $secondFactorId,
209
        YubikeyPublicId $yubikeyPublicId,
210
        int             $maxNumberOfTokens,
211
    ): void {
212
        $this->assertNotForgotten();
213
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
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,
231
        YubikeyPublicId         $yubikeyPublicId,
232
        bool                    $emailVerificationRequired,
233
        EmailVerificationWindow $emailVerificationWindow,
234
        int                     $maxNumberOfTokens,
235
    ): void {
236
        $this->assertNotForgotten();
237
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
238
239
        if ($emailVerificationRequired) {
240
            $emailVerificationNonce = TokenGenerator::generateNonce();
241
242
            $this->apply(
243
                new YubikeyPossessionProvenEvent(
244
                    $this->id,
245
                    $this->institution,
246
                    $secondFactorId,
247
                    $yubikeyPublicId,
248
                    $emailVerificationRequired,
249
                    $emailVerificationWindow,
250
                    $emailVerificationNonce,
251
                    $this->commonName,
252
                    $this->email,
253
                    $this->preferredLocale,
254
                ),
255
            );
256
        } else {
257
            $this->apply(
258
                new YubikeyPossessionProvenAndVerifiedEvent(
259
                    $this->id,
260
                    $this->institution,
261
                    $secondFactorId,
262
                    $yubikeyPublicId,
263
                    $this->commonName,
264
                    $this->email,
265
                    $this->preferredLocale,
266
                    DateTime::now(),
267
                    OtpGenerator::generate(8),
268
                ),
269
            );
270
        }
271
    }
272
273
    public function provePossessionOfPhone(
274
        SecondFactorId          $secondFactorId,
275
        PhoneNumber             $phoneNumber,
276
        bool                    $emailVerificationRequired,
277
        EmailVerificationWindow $emailVerificationWindow,
278
        int                     $maxNumberOfTokens,
279
    ): void {
280
        $this->assertNotForgotten();
281
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
282
283
        if ($emailVerificationRequired) {
284
            $emailVerificationNonce = TokenGenerator::generateNonce();
285
286
            $this->apply(
287
                new PhonePossessionProvenEvent(
288
                    $this->id,
289
                    $this->institution,
290
                    $secondFactorId,
291
                    $phoneNumber,
292
                    $emailVerificationRequired,
293
                    $emailVerificationWindow,
294
                    $emailVerificationNonce,
295
                    $this->commonName,
296
                    $this->email,
297
                    $this->preferredLocale,
298
                ),
299
            );
300
        } else {
301
            $this->apply(
302
                new PhonePossessionProvenAndVerifiedEvent(
303
                    $this->id,
304
                    $this->institution,
305
                    $secondFactorId,
306
                    $phoneNumber,
307
                    $this->commonName,
308
                    $this->email,
309
                    $this->preferredLocale,
310
                    DateTime::now(),
311
                    OtpGenerator::generate(8),
312
                ),
313
            );
314
        }
315
    }
316
317
    public function provePossessionOfPhoneRecoveryToken(
318
        RecoveryTokenId $recoveryTokenId,
319
        PhoneNumber $phoneNumber,
320
    ): void {
321
        $this->assertNotForgotten();
322
        $this->assertUserMayAddRecoveryToken(RecoveryTokenType::sms());
323
        $this->apply(
324
            new PhoneRecoveryTokenPossessionProvenEvent(
325
                $this->id,
326
                $this->institution,
327
                $recoveryTokenId,
328
                $phoneNumber,
329
                $this->commonName,
330
                $this->email,
331
                $this->preferredLocale,
332
            ),
333
        );
334
    }
335
336
337
    public function promisePossessionOfSafeStoreSecretRecoveryToken(RecoveryTokenId $tokenId, SafeStore $secret): void
338
    {
339
        $this->assertNotForgotten();
340
        $this->assertUserMayAddRecoveryToken(RecoveryTokenType::safeStore());
341
        $this->apply(
342
            new SafeStoreSecretRecoveryTokenPossessionPromisedEvent(
343
                $this->id,
344
                $this->institution,
345
                $tokenId,
346
                $secret,
347
                $this->commonName,
348
                $this->email,
349
                $this->preferredLocale,
350
            ),
351
        );
352
    }
353
354
    public function saveVettingTypeHints(Institution $institution, VettingTypeHintCollection $hints): void
355
    {
356
        $this->assertNotForgotten();
357
        $this->apply(
358
            new VettingTypeHintsSavedEvent(
359
                $this->id,
360
                $this->institution,
361
                $hints,
362
                $institution,
363
            ),
364
        );
365
    }
366
367
    public function provePossessionOfGssf(
368
        SecondFactorId          $secondFactorId,
369
        StepupProvider          $provider,
370
        GssfId                  $gssfId,
371
        bool                    $emailVerificationRequired,
372
        EmailVerificationWindow $emailVerificationWindow,
373
        int                     $maxNumberOfTokens,
374
    ): void {
375
        $this->assertNotForgotten();
376
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
377
378
        if ($emailVerificationRequired) {
379
            $emailVerificationNonce = TokenGenerator::generateNonce();
380
381
            $this->apply(
382
                new GssfPossessionProvenEvent(
383
                    $this->id,
384
                    $this->institution,
385
                    $secondFactorId,
386
                    $provider,
387
                    $gssfId,
388
                    $emailVerificationRequired,
389
                    $emailVerificationWindow,
390
                    $emailVerificationNonce,
391
                    $this->commonName,
392
                    $this->email,
393
                    $this->preferredLocale,
394
                ),
395
            );
396
        } else {
397
            $this->apply(
398
                new GssfPossessionProvenAndVerifiedEvent(
399
                    $this->id,
400
                    $this->institution,
401
                    $secondFactorId,
402
                    $provider,
403
                    $gssfId,
404
                    $this->commonName,
405
                    $this->email,
406
                    $this->preferredLocale,
407
                    DateTime::now(),
408
                    OtpGenerator::generate(8),
409
                ),
410
            );
411
        }
412
    }
413
414
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $secondFactorId should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $keyHandle should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $emailVerificationRequired should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $emailVerificationWindow should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $maxNumberOfTokens should have a doc-comment as per coding-style.
Loading history...
415
     * @deprecated Built in U2F support is dropped from StepUp, this was not removed to support event replay
416
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
417
    public function provePossessionOfU2fDevice(
418
        SecondFactorId          $secondFactorId,
419
        U2fKeyHandle            $keyHandle,
420
        bool                    $emailVerificationRequired,
421
        EmailVerificationWindow $emailVerificationWindow,
422
        int                     $maxNumberOfTokens,
423
    ): void {
424
        $this->assertNotForgotten();
425
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
426
427
        if ($emailVerificationRequired) {
428
            $emailVerificationNonce = TokenGenerator::generateNonce();
429
430
            $this->apply(
431
                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

431
                /** @scrutinizer ignore-deprecated */ new U2fDevicePossessionProvenEvent(
Loading history...
432
                    $this->id,
433
                    $this->institution,
434
                    $secondFactorId,
435
                    $keyHandle,
436
                    $emailVerificationRequired,
437
                    $emailVerificationWindow,
438
                    $emailVerificationNonce,
439
                    $this->commonName,
440
                    $this->email,
441
                    $this->preferredLocale,
442
                ),
443
            );
444
        } else {
445
            $this->apply(
446
                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

446
                /** @scrutinizer ignore-deprecated */ new U2fDevicePossessionProvenAndVerifiedEvent(
Loading history...
447
                    $this->id,
448
                    $this->institution,
449
                    $secondFactorId,
450
                    $keyHandle,
451
                    $this->commonName,
452
                    $this->email,
453
                    $this->preferredLocale,
454
                    DateTime::now(),
455
                    OtpGenerator::generate(8),
456
                ),
457
            );
458
        }
459
    }
460
461
    public function verifyEmail(string $verificationNonce): void
462
    {
463
        $this->assertNotForgotten();
464
465
        $secondFactorToVerify = null;
466
        foreach ($this->unverifiedSecondFactors as $secondFactor) {
467
            /** @var Entity\UnverifiedSecondFactor $secondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
468
            if ($secondFactor->hasNonce($verificationNonce)) {
469
                $secondFactorToVerify = $secondFactor;
470
            }
471
        }
472
473
        if (!$secondFactorToVerify) {
474
            throw new DomainException(
475
                'Cannot verify second factor, no unverified second factor can be verified using the given nonce',
476
            );
477
        }
478
479
        /** @var Entity\UnverifiedSecondFactor $secondFactorToVerify */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
480
        if (!$secondFactorToVerify->canBeVerifiedNow()) {
481
            throw new DomainException('Cannot verify second factor, the verification window is closed.');
482
        }
483
484
        $secondFactorToVerify->verifyEmail();
485
    }
486
487
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $registrant should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $registrantsSecondFactorId should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $registrantsSecondFactorType should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $registrantsSecondFactorIdentifier should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $registrationCode should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $documentNumber should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $identityVerified should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $secondFactorTypeService should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $secondFactorProvePossessionHelper should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $provePossessionSkipped should have a doc-comment as per coding-style.
Loading history...
488
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
489
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
490
    public function vetSecondFactor(
491
        IdentityApi                       $registrant,
492
        SecondFactorId                    $registrantsSecondFactorId,
493
        SecondFactorType                  $registrantsSecondFactorType,
494
        SecondFactorIdentifier            $registrantsSecondFactorIdentifier,
495
        string                            $registrationCode,
496
        DocumentNumber                    $documentNumber,
497
        bool                              $identityVerified,
498
        SecondFactorTypeService           $secondFactorTypeService,
499
        SecondFactorProvePossessionHelper $secondFactorProvePossessionHelper,
500
        bool                              $provePossessionSkipped,
501
    ): void {
502
        $this->assertNotForgotten();
503
504
        /** The vetted second factor collection can determine highest loa based on the vetting type,
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Doc comment short description must be on the first line
Loading history...
505
         * the other can not (as the verified and unverified second factors do not have a vetting type)
506
         * And the vetting type is used to determine if the LoA is diminished (in case of a self
507
         * asserted token registration)
508
         */
509
        /** @var VettedSecondFactor|null $secondFactorWithHighestLoa */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
510
        $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

510
        /** @scrutinizer ignore-call */ 
511
        $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...
511
            $secondFactorTypeService,
512
        );
513
        $registrantsSecondFactor = $registrant->getVerifiedSecondFactor($registrantsSecondFactorId);
514
515
        if (!$registrantsSecondFactor instanceof \Surfnet\Stepup\Identity\Entity\VerifiedSecondFactor) {
516
            throw new DomainException(
517
                sprintf('Registrant second factor with ID %s does not exist', $registrantsSecondFactorId),
518
            );
519
        }
520
521
        if ($secondFactorWithHighestLoa === null) {
522
            throw new DomainException(
523
                sprintf(
524
                    'Vetting failed: authority %s has %d vetted second factors!',
525
                    $this->id,
526
                    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

526
                    count(/** @scrutinizer ignore-type */ $this->vettedSecondFactors),
Loading history...
527
                ),
528
            );
529
        }
530
531
        if (!$secondFactorWithHighestLoa->hasEqualOrHigherLoaComparedTo(
532
            $registrantsSecondFactor,
533
            $secondFactorTypeService,
534
        )) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
535
            throw new DomainException("Authority does not have the required LoA to vet the registrant's second factor");
536
        }
537
538
        if (!$identityVerified) {
539
            throw new DomainException('Will not vet second factor when physical identity has not been verified.');
540
        }
541
542
        if ($provePossessionSkipped && !$secondFactorProvePossessionHelper->canSkipProvePossession(
543
            $registrantsSecondFactorType,
544
        )) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
545
            throw new DomainException(
546
                sprintf(
547
                    "The possession of registrants second factor with ID '%s' of type '%s' has to be physically proven",
548
                    $registrantsSecondFactorId,
549
                    $registrantsSecondFactorType->getSecondFactorType(),
550
                ),
551
            );
552
        }
553
554
        $registrant->complyWithVettingOfSecondFactor(
555
            $registrantsSecondFactorId,
556
            $registrantsSecondFactorType,
557
            $registrantsSecondFactorIdentifier,
558
            $registrationCode,
559
            $documentNumber,
560
            $provePossessionSkipped,
561
        );
562
    }
563
564
    public function registerSelfAssertedSecondFactor(
565
        SecondFactorIdentifier $secondFactorIdentifier,
566
        SecondFactorTypeService $secondFactorTypeService,
567
        RecoveryTokenId $recoveryTokenId,
568
    ): void {
569
        $this->assertNotForgotten();
570
        $this->assertSelfAssertedTokenRegistrationAllowed();
571
572
        try {
573
            $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

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

715
                /** @scrutinizer ignore-type */ $secondFactor->getId(),
Loading history...
716
                new SecondFactorId($targetSecondFactorId),
717
                $secondFactor->getType(),
718
                $secondFactor->getIdentifier(),
719
            ),
720
        );
721
    }
722
723
    public function complyWithVettingOfSecondFactor(
724
        SecondFactorId         $secondFactorId,
725
        SecondFactorType       $secondFactorType,
726
        SecondFactorIdentifier $secondFactorIdentifier,
727
        string                 $registrationCode,
728
        DocumentNumber         $documentNumber,
729
        bool                   $provePossessionSkipped,
730
    ): void {
731
        $this->assertNotForgotten();
732
733
        $secondFactorToVet = null;
734
        foreach ($this->verifiedSecondFactors as $secondFactor) {
735
            /** @var VerifiedSecondFactor $secondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
736
            if ($secondFactor->hasRegistrationCodeAndIdentifier($registrationCode, $secondFactorIdentifier)) {
737
                $secondFactorToVet = $secondFactor;
738
            }
739
        }
740
741
        if (!$secondFactorToVet) {
742
            throw new DomainException(
743
                'Cannot vet second factor, no verified second factor can be vetted using the given registration code ' .
744
                'and second factor identifier',
745
            );
746
        }
747
748
        if (!$secondFactorToVet->canBeVettedNow()) {
749
            throw new DomainException('Cannot vet second factor, the registration window is closed.');
750
        }
751
752
        $secondFactorToVet->vet($provePossessionSkipped, new OnPremiseVettingType($documentNumber));
753
    }
754
755
    public function revokeSecondFactor(SecondFactorId $secondFactorId): void
756
    {
757
        $this->assertNotForgotten();
758
759
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
760
        $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

760
        /** @scrutinizer ignore-call */ 
761
        $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...
761
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
762
        $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

762
        /** @scrutinizer ignore-call */ 
763
        $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...
763
        /** @var VettedSecondFactor|null $vettedSecondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
764
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
765
766
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
767
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
768
        }
769
770
        if ($unverifiedSecondFactor) {
771
            $unverifiedSecondFactor->revoke();
772
773
            return;
774
        }
775
776
        if ($verifiedSecondFactor) {
777
            $verifiedSecondFactor->revoke();
778
779
            return;
780
        }
781
782
        $vettedSecondFactor->revoke();
783
784
        if ($this->vettedSecondFactors->isEmpty()) {
785
            $this->allVettedSecondFactorsRemoved();
786
        }
787
    }
788
789
    public function complyWithSecondFactorRevocation(SecondFactorId $secondFactorId, IdentityId $authorityId): void
790
    {
791
        $this->assertNotForgotten();
792
793
        /** @var UnverifiedSecondFactor|null $unverifiedSecondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
794
        $unverifiedSecondFactor = $this->unverifiedSecondFactors->get((string)$secondFactorId);
795
        /** @var VerifiedSecondFactor|null $verifiedSecondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
796
        $verifiedSecondFactor = $this->verifiedSecondFactors->get((string)$secondFactorId);
797
        /** @var VettedSecondFactor|null $vettedSecondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
798
        $vettedSecondFactor = $this->vettedSecondFactors->get((string)$secondFactorId);
799
800
        if (!$unverifiedSecondFactor && !$verifiedSecondFactor && !$vettedSecondFactor) {
801
            throw new DomainException('Cannot revoke second factor: no second factor with given id exists.');
802
        }
803
804
        if ($unverifiedSecondFactor) {
805
            $unverifiedSecondFactor->complyWithRevocation($authorityId);
806
807
            return;
808
        }
809
810
        if ($verifiedSecondFactor) {
811
            $verifiedSecondFactor->complyWithRevocation($authorityId);
812
813
            return;
814
        }
815
816
        $vettedSecondFactor->complyWithRevocation($authorityId);
817
818
        if ($this->vettedSecondFactors->isEmpty()) {
819
            $this->allVettedSecondFactorsRemoved();
820
        }
821
    }
822
823
    public function revokeRecoveryToken(RecoveryTokenId $recoveryTokenId): void
824
    {
825
        $this->assertNotForgotten();
826
        try {
827
            $recoveryToken = $this->recoveryTokens->get($recoveryTokenId);
828
        } catch (DomainException $e) {
829
            throw new DomainException('Cannot revoke recovery token: no token with given id exists.', 0, $e);
830
        }
831
        $recoveryToken->revoke();
832
    }
833
834
    public function complyWithRecoveryTokenRevocation(RecoveryTokenId $recoveryTokenId, IdentityId $authorityId): void
835
    {
836
        $this->assertNotForgotten();
837
        try {
838
            $recoveryToken = $this->recoveryTokens->get($recoveryTokenId);
839
        } catch (DomainException $e) {
840
            throw new DomainException('Cannot revoke recovery token: no token with given id exists.', 0, $e);
841
        }
842
        $recoveryToken->complyWithRevocation($authorityId);
843
    }
844
845
    /**
846
     * @param RegistrationAuthorityRole $role
0 ignored issues
show
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
847
     * @param Institution $institution
0 ignored issues
show
Coding Style introduced by
Expected 15 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
848
     * @param Location $location
0 ignored issues
show
Coding Style introduced by
Expected 18 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
849
     * @param ContactInformation $contactInformation
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
850
     * @param InstitutionConfiguration $institutionConfiguration
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
851
     * @return void
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
852
     */
853
    public function accreditWith(
854
        RegistrationAuthorityRole $role,
855
        Institution $institution,
856
        Location $location,
857
        ContactInformation $contactInformation,
858
        InstitutionConfiguration $institutionConfiguration,
859
    ): void {
860
        $this->assertNotForgotten();
861
862
        if (!$institutionConfiguration->isInstitutionAllowedToAccreditRoles(
863
            new ConfigurationInstitution($this->institution->getInstitution()),
864
        )) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
865
            throw new DomainException('An Identity may only be accredited by configured institutions.');
866
        }
867
868
        if (!$this->vettedSecondFactors->count()) {
869
            throw new DomainException(
870
                'An Identity must have at least one vetted second factor before it can be accredited',
871
            );
872
        }
873
874
        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

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

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

1364
    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...
1365
    {
1366
        $this->commonName = CommonName::unknown();
1367
        $this->email = Email::unknown();
1368
        $this->forgotten = true;
1369
    }
1370
1371
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1372
     * This method is kept to be backwards compatible for changes before FGA
1373
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1374
    protected function applyAppointedAsRaEvent(AppointedAsRaEvent $event): void
1375
    {
1376
        $this->registrationAuthorities->get($event->identityInstitution)
1377
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1378
    }
1379
1380
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1381
     * This method is kept to be backwards compatible for changes before FGA
1382
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1383
    protected function applyAppointedAsRaaEvent(AppointedAsRaaEvent $event): void
1384
    {
1385
        $this->registrationAuthorities->get($event->identityInstitution)
1386
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RAA));
1387
    }
1388
1389
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1390
     * This method is kept to be backwards compatible for changes before FGA
1391
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1392
    protected function applyIdentityAccreditedAsRaEvent(IdentityAccreditedAsRaEvent $event): void
1393
    {
1394
        $this->registrationAuthorities->set(
1395
            $event->identityInstitution,
1396
            RegistrationAuthority::accreditWith(
1397
                $event->registrationAuthorityRole,
1398
                $event->location,
1399
                $event->contactInformation,
1400
                $event->identityInstitution,
1401
            ),
1402
        );
1403
    }
1404
1405
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1406
     * This method is kept to be backwards compatible for changes before FGA
1407
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1408
    protected function applyIdentityAccreditedAsRaaEvent(IdentityAccreditedAsRaaEvent $event): void
1409
    {
1410
        $this->registrationAuthorities->set(
1411
            $event->identityInstitution,
1412
            RegistrationAuthority::accreditWith(
1413
                $event->registrationAuthorityRole,
1414
                $event->location,
1415
                $event->contactInformation,
1416
                $event->identityInstitution,
1417
            ),
1418
        );
1419
    }
1420
1421
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1422
     * This method is kept to be backwards compatible for changes before FGA
1423
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1424
    protected function applyAppointedAsRaForInstitutionEvent(AppointedAsRaForInstitutionEvent $event): void
1425
    {
1426
        $this->registrationAuthorities->get($event->identityInstitution)
1427
            ->appointAs(new RegistrationAuthorityRole(RegistrationAuthorityRole::ROLE_RA));
1428
    }
1429
1430
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1431
     * This method is kept to be backwards compatible for changes before FGA
1432
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1433
    protected function applyRegistrationAuthorityInformationAmendedEvent(
1434
        RegistrationAuthorityInformationAmendedEvent $event,
1435
    ): void {
1436
        $this->registrationAuthorities->get($event->identityInstitution)->amendInformation(
1437
            $event->location,
1438
            $event->contactInformation,
1439
        );
1440
    }
1441
1442
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $event should have a doc-comment as per coding-style.
Loading history...
1443
     * This method is kept to be backwards compatible for changes before FGA
1444
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1445
    protected function applyRegistrationAuthorityRetractedEvent(RegistrationAuthorityRetractedEvent $event): void
1446
    {
1447
        $this->registrationAuthorities->remove($event->identityInstitution);
1448
    }
1449
1450
1451
    public function getAggregateRootId(): string
1452
    {
1453
        return $this->id->getIdentityId();
1454
    }
1455
1456
    protected function getChildEntities(): array
1457
    {
1458
        return array_merge(
1459
            $this->unverifiedSecondFactors->getValues(),
1460
            $this->verifiedSecondFactors->getValues(),
1461
            $this->vettedSecondFactors->getValues(),
1462
            $this->registrationAuthorities->getValues(),
1463
        );
1464
    }
1465
1466
    /**
1467
     * @throws DomainException
1468
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1469
    private function assertNotForgotten(): void
0 ignored issues
show
Coding Style introduced by
Private method name "Identity::assertNotForgotten" must be prefixed with an underscore
Loading history...
1470
    {
1471
        if ($this->forgotten) {
1472
            throw new IdentityForgottenException('Operation on this Identity is not allowed: it has been forgotten');
1473
        }
1474
    }
1475
1476
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $maxNumberOfTokens should have a doc-comment as per coding-style.
Loading history...
1477
     * @throws DomainException
1478
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
1479
    private function assertUserMayAddSecondFactor(int $maxNumberOfTokens): void
0 ignored issues
show
Coding Style introduced by
Private method name "Identity::assertUserMayAddSecondFactor" must be prefixed with an underscore
Loading history...
1480
    {
1481
        if (count($this->unverifiedSecondFactors) +
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

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