applyCompliedWithVerifiedSecondFactorRevocationEvent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 4
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\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\IdentityRestoredEvent;
56
use Surfnet\Stepup\Identity\Event\IdentityRenamedEvent;
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
 */
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...
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,
208
        YubikeyPublicId $yubikeyPublicId,
209
        int             $maxNumberOfTokens,
210
    ): void {
211
        $this->assertNotForgotten();
212
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
213
214
        $this->apply(
215
            new YubikeySecondFactorBootstrappedEvent(
216
                $this->id,
217
                $this->nameId,
218
                $this->institution,
219
                $this->commonName,
220
                $this->email,
221
                $this->preferredLocale,
222
                $secondFactorId,
223
                $yubikeyPublicId,
224
            ),
225
        );
226
    }
227
228
    public function provePossessionOfYubikey(
229
        SecondFactorId          $secondFactorId,
230
        YubikeyPublicId         $yubikeyPublicId,
231
        bool                    $emailVerificationRequired,
232
        EmailVerificationWindow $emailVerificationWindow,
233
        int                     $maxNumberOfTokens,
234
    ): void {
235
        $this->assertNotForgotten();
236
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
237
238
        if ($emailVerificationRequired) {
239
            $emailVerificationNonce = TokenGenerator::generateNonce();
240
241
            $this->apply(
242
                new YubikeyPossessionProvenEvent(
243
                    $this->id,
244
                    $this->institution,
245
                    $secondFactorId,
246
                    $yubikeyPublicId,
247
                    $emailVerificationRequired,
248
                    $emailVerificationWindow,
249
                    $emailVerificationNonce,
250
                    $this->commonName,
251
                    $this->email,
252
                    $this->preferredLocale,
253
                ),
254
            );
255
        } else {
256
            $this->apply(
257
                new YubikeyPossessionProvenAndVerifiedEvent(
258
                    $this->id,
259
                    $this->institution,
260
                    $secondFactorId,
261
                    $yubikeyPublicId,
262
                    $this->commonName,
263
                    $this->email,
264
                    $this->preferredLocale,
265
                    DateTime::now(),
266
                    OtpGenerator::generate(8),
267
                ),
268
            );
269
        }
270
    }
271
272
    public function provePossessionOfPhone(
273
        SecondFactorId          $secondFactorId,
274
        PhoneNumber             $phoneNumber,
275
        bool                    $emailVerificationRequired,
276
        EmailVerificationWindow $emailVerificationWindow,
277
        int                     $maxNumberOfTokens,
278
    ): void {
279
        $this->assertNotForgotten();
280
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
281
282
        if ($emailVerificationRequired) {
283
            $emailVerificationNonce = TokenGenerator::generateNonce();
284
285
            $this->apply(
286
                new PhonePossessionProvenEvent(
287
                    $this->id,
288
                    $this->institution,
289
                    $secondFactorId,
290
                    $phoneNumber,
291
                    $emailVerificationRequired,
292
                    $emailVerificationWindow,
293
                    $emailVerificationNonce,
294
                    $this->commonName,
295
                    $this->email,
296
                    $this->preferredLocale,
297
                ),
298
            );
299
        } else {
300
            $this->apply(
301
                new PhonePossessionProvenAndVerifiedEvent(
302
                    $this->id,
303
                    $this->institution,
304
                    $secondFactorId,
305
                    $phoneNumber,
306
                    $this->commonName,
307
                    $this->email,
308
                    $this->preferredLocale,
309
                    DateTime::now(),
310
                    OtpGenerator::generate(8),
311
                ),
312
            );
313
        }
314
    }
315
316
    public function provePossessionOfPhoneRecoveryToken(
317
        RecoveryTokenId $recoveryTokenId,
318
        PhoneNumber $phoneNumber,
319
    ): void {
320
        $this->assertNotForgotten();
321
        $this->assertUserMayAddRecoveryToken(RecoveryTokenType::sms());
322
        $this->apply(
323
            new PhoneRecoveryTokenPossessionProvenEvent(
324
                $this->id,
325
                $this->institution,
326
                $recoveryTokenId,
327
                $phoneNumber,
328
                $this->commonName,
329
                $this->email,
330
                $this->preferredLocale,
331
            ),
332
        );
333
    }
334
335
336
    public function promisePossessionOfSafeStoreSecretRecoveryToken(RecoveryTokenId $tokenId, SafeStore $secret): void
337
    {
338
        $this->assertNotForgotten();
339
        $this->assertUserMayAddRecoveryToken(RecoveryTokenType::safeStore());
340
        $this->apply(
341
            new SafeStoreSecretRecoveryTokenPossessionPromisedEvent(
342
                $this->id,
343
                $this->institution,
344
                $tokenId,
345
                $secret,
346
                $this->commonName,
347
                $this->email,
348
                $this->preferredLocale,
349
            ),
350
        );
351
    }
352
353
    public function saveVettingTypeHints(Institution $institution, VettingTypeHintCollection $hints): void
354
    {
355
        $this->assertNotForgotten();
356
        $this->apply(
357
            new VettingTypeHintsSavedEvent(
358
                $this->id,
359
                $this->institution,
360
                $hints,
361
                $institution,
362
            ),
363
        );
364
    }
365
366
    public function provePossessionOfGssf(
367
        SecondFactorId          $secondFactorId,
368
        StepupProvider          $provider,
369
        GssfId                  $gssfId,
370
        bool                    $emailVerificationRequired,
371
        EmailVerificationWindow $emailVerificationWindow,
372
        int                     $maxNumberOfTokens,
373
    ): void {
374
        $this->assertNotForgotten();
375
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
376
377
        if ($emailVerificationRequired) {
378
            $emailVerificationNonce = TokenGenerator::generateNonce();
379
380
            $this->apply(
381
                new GssfPossessionProvenEvent(
382
                    $this->id,
383
                    $this->institution,
384
                    $secondFactorId,
385
                    $provider,
386
                    $gssfId,
387
                    $emailVerificationRequired,
388
                    $emailVerificationWindow,
389
                    $emailVerificationNonce,
390
                    $this->commonName,
391
                    $this->email,
392
                    $this->preferredLocale,
393
                ),
394
            );
395
        } else {
396
            $this->apply(
397
                new GssfPossessionProvenAndVerifiedEvent(
398
                    $this->id,
399
                    $this->institution,
400
                    $secondFactorId,
401
                    $provider,
402
                    $gssfId,
403
                    $this->commonName,
404
                    $this->email,
405
                    $this->preferredLocale,
406
                    DateTime::now(),
407
                    OtpGenerator::generate(8),
408
                ),
409
            );
410
        }
411
    }
412
413
    /**
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...
414
     * @deprecated Built in U2F support is dropped from StepUp, this was not removed to support event replay
415
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
416
    public function provePossessionOfU2fDevice(
417
        SecondFactorId          $secondFactorId,
418
        U2fKeyHandle            $keyHandle,
419
        bool                    $emailVerificationRequired,
420
        EmailVerificationWindow $emailVerificationWindow,
421
        int                     $maxNumberOfTokens,
422
    ): void {
423
        $this->assertNotForgotten();
424
        $this->assertUserMayAddSecondFactor($maxNumberOfTokens);
425
426
        if ($emailVerificationRequired) {
427
            $emailVerificationNonce = TokenGenerator::generateNonce();
428
429
            $this->apply(
430
                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

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

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

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

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

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

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

759
        /** @scrutinizer ignore-call */ 
760
        $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...
760
        /** @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...
761
        $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

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

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

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

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

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