handleProvePhoneRecoveryTokenPossessionCommand()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 0
loc 12
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\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler;
20
21
use Broadway\CommandHandling\SimpleCommandHandler;
22
use Broadway\Repository\Repository as RepositoryInterface;
23
use Surfnet\Stepup\Configuration\Value\Institution as ConfigurationInstitution;
24
use Surfnet\Stepup\Helper\RecoveryTokenSecretHelper;
25
use Surfnet\Stepup\Helper\SecondFactorProvePossessionHelper;
26
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
27
use Surfnet\Stepup\Identity\Entity\ConfigurableSettings;
28
use Surfnet\Stepup\Identity\Identity;
29
use Surfnet\Stepup\Identity\Value\CommonName;
30
use Surfnet\Stepup\Identity\Value\DocumentNumber;
31
use Surfnet\Stepup\Identity\Value\Email;
32
use Surfnet\Stepup\Identity\Value\GssfId;
33
use Surfnet\Stepup\Identity\Value\IdentityId;
34
use Surfnet\Stepup\Identity\Value\Institution;
35
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...
36
use Surfnet\Stepup\Identity\Value\NameId;
37
use Surfnet\Stepup\Identity\Value\PhoneNumber;
38
use Surfnet\Stepup\Identity\Value\RecoveryTokenId;
39
use Surfnet\Stepup\Identity\Value\SafeStore;
40
use Surfnet\Stepup\Identity\Value\SecondFactorId;
41
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifierFactory;
42
use Surfnet\Stepup\Identity\Value\StepupProvider;
43
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
44
use Surfnet\Stepup\Identity\Value\UnhashedSecret;
45
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
46
use Surfnet\StepupBundle\Service\LoaResolutionService;
47
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
48
use Surfnet\StepupBundle\Value\SecondFactorType;
49
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
50
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
51
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\RuntimeException;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnknownLoaException;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
56
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
57
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
58
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
59
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\MigrateVettedSecondFactorCommand;
60
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\PromiseSafeStoreSecretTokenPossessionCommand;
61
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
62
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
63
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhoneRecoveryTokenPossessionCommand;
64
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveU2fDevicePossessionCommand;
65
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveYubikeyPossessionCommand;
66
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RegisterSelfAssertedSecondFactorCommand;
67
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnRecoveryTokenCommand;
68
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
69
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsRecoveryTokenCommand;
70
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
71
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\SelfVetSecondFactorCommand;
72
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\SendSecondFactorRegistrationEmailCommand;
73
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
74
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
75
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
76
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler\Exception\DuplicateIdentityException;
77
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Service\RegistrationMailService;
78
79
/**
80
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
81
 * @SuppressWarnings(PHPMD.TooManyMethods)
82
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
83
 */
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...
84
class IdentityCommandHandler extends SimpleCommandHandler
85
{
86
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $eventSourcedRepository should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $identityProjectionRepository should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $configurableSettings should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $allowedSecondFactorListService 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 $provePossessionHelper should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $institutionConfigurationOptionsService should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $loaResolutionService should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $recoveryTokenSecretHelper should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $registrationMailService should have a doc-comment as per coding-style.
Loading history...
87
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
88
     */
89
    public function __construct(
90
        private readonly RepositoryInterface $eventSourcedRepository,
91
        private readonly IdentityRepository $identityProjectionRepository,
92
        private readonly ConfigurableSettings $configurableSettings,
93
        private readonly AllowedSecondFactorListService $allowedSecondFactorListService,
94
        private readonly SecondFactorTypeService $secondFactorTypeService,
95
        private readonly SecondFactorProvePossessionHelper $provePossessionHelper,
96
        private readonly InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
97
        private readonly LoaResolutionService $loaResolutionService,
98
        private readonly RecoveryTokenSecretHelper $recoveryTokenSecretHelper,
99
        private readonly RegistrationMailService $registrationMailService,
100
    ) {
101
    }
102
103
    public function handleCreateIdentityCommand(CreateIdentityCommand $command): void
104
    {
105
        $preferredLocale = new Locale($command->preferredLocale);
106
        $this->assertIsValidLocale($preferredLocale);
107
108
        $identity = Identity::create(
109
            new IdentityId($command->id),
110
            new Institution($command->institution),
111
            new NameId($command->nameId),
112
            new CommonName($command->commonName),
113
            new Email($command->email),
114
            $preferredLocale,
115
        );
116
117
        $this->eventSourcedRepository->save($identity);
118
    }
119
120
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command): void
121
    {
122
        /** @var IdentityApi $identity */
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...
123
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->id));
124
125
        $identity->restore(new CommonName($command->commonName), new Email($command->email));
126
        $identity->rename(new CommonName($command->commonName));
127
        $identity->changeEmail(new Email($command->email));
128
129
        $this->eventSourcedRepository->save($identity);
130
    }
131
132
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
133
        BootstrapIdentityWithYubikeySecondFactorCommand $command,
134
    ): void {
135
        $preferredLocale = new Locale($command->preferredLocale);
136
        $this->assertIsValidLocale($preferredLocale);
137
138
        $institution = new Institution($command->institution);
139
        $nameId = new NameId($command->nameId);
140
141
        if ($this->identityProjectionRepository->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
142
            throw DuplicateIdentityException::forBootstrappingWithYubikeySecondFactor($nameId, $institution);
143
        }
144
145
        $identity = Identity::create(
146
            new IdentityId($command->identityId),
147
            $institution,
148
            $nameId,
149
            new CommonName($command->commonName),
150
            new Email($command->email),
151
            $preferredLocale,
152
        );
153
154
        $configurationInstitution = new ConfigurationInstitution(
155
            (string)$identity->getInstitution(),
156
        );
157
158
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
159
160
        $identity->bootstrapYubikeySecondFactor(
161
            new SecondFactorId($command->secondFactorId),
162
            new YubikeyPublicId($command->yubikeyPublicId),
163
            $tokenCount,
164
        );
165
166
        $this->eventSourcedRepository->save($identity);
167
    }
168
169
    public function handleProveYubikeyPossessionCommand(ProveYubikeyPossessionCommand $command): void
170
    {
171
        /** @var IdentityApi $identity */
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...
172
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
173
174
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('yubikey'), $identity->getInstitution());
175
176
        $configurationInstitution = new ConfigurationInstitution(
177
            (string)$identity->getInstitution(),
178
        );
179
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
180
181
        $identity->provePossessionOfYubikey(
182
            new SecondFactorId($command->secondFactorId),
183
            new YubikeyPublicId($command->yubikeyPublicId),
184
            $this->emailVerificationIsRequired($identity),
185
            $this->configurableSettings->createNewEmailVerificationWindow(),
186
            $tokenCount,
187
        );
188
189
        $this->eventSourcedRepository->save($identity);
190
    }
191
192
    public function handleProvePhonePossessionCommand(ProvePhonePossessionCommand $command): void
193
    {
194
        /** @var IdentityApi $identity */
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...
195
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
196
197
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('sms'), $identity->getInstitution());
198
199
        $configurationInstitution = new ConfigurationInstitution(
200
            (string)$identity->getInstitution(),
201
        );
202
203
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
204
205
        $identity->provePossessionOfPhone(
206
            new SecondFactorId($command->secondFactorId),
207
            new PhoneNumber($command->phoneNumber),
208
            $this->emailVerificationIsRequired($identity),
209
            $this->configurableSettings->createNewEmailVerificationWindow(),
210
            $tokenCount,
211
        );
212
213
        $this->eventSourcedRepository->save($identity);
214
    }
215
216
    public function handleProveGssfPossessionCommand(ProveGssfPossessionCommand $command): void
217
    {
218
        /** @var IdentityApi $identity */
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...
219
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
220
        $secondFactorType = $command->stepupProvider;
221
222
        // Validate that the chosen second factor type (stepupProvider) is allowed for the users instituti
223
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType($secondFactorType), $identity->getInstitution());
224
225
        $configurationInstitution = new ConfigurationInstitution(
226
            (string)$identity->getInstitution(),
227
        );
228
229
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
230
231
        $identity->provePossessionOfGssf(
232
            new SecondFactorId($command->secondFactorId),
233
            new StepupProvider($secondFactorType),
234
            new GssfId($command->gssfId),
235
            $this->emailVerificationIsRequired($identity),
236
            $this->configurableSettings->createNewEmailVerificationWindow(),
237
            $tokenCount,
238
        );
239
240
        $this->eventSourcedRepository->save($identity);
241
    }
242
243
    public function handleProveU2fDevicePossessionCommand(ProveU2fDevicePossessionCommand $command): void
244
    {
245
        /** @var IdentityApi $identity */
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...
246
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
247
248
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
249
250
        $configurationInstitution = new ConfigurationInstitution(
251
            (string)$identity->getInstitution(),
252
        );
253
254
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
255
256
        $identity->provePossessionOfU2fDevice(
0 ignored issues
show
Deprecated Code introduced by
The function Surfnet\Stepup\Identity\...PossessionOfU2fDevice() has been deprecated: Built in U2F support is dropped from StepUp, this 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

256
        /** @scrutinizer ignore-deprecated */ $identity->provePossessionOfU2fDevice(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
257
            new SecondFactorId($command->secondFactorId),
258
            new U2fKeyHandle($command->keyHandle),
0 ignored issues
show
Deprecated Code introduced by
The class Surfnet\Stepup\Identity\Value\U2fKeyHandle has been deprecated: Built in U2F support is dropped from StepUp, this 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

258
            /** @scrutinizer ignore-deprecated */ new U2fKeyHandle($command->keyHandle),
Loading history...
259
            $this->emailVerificationIsRequired($identity),
260
            $this->configurableSettings->createNewEmailVerificationWindow(),
261
            $tokenCount,
262
        );
263
264
        $this->eventSourcedRepository->save($identity);
265
    }
266
267
    public function handleVerifyEmailCommand(VerifyEmailCommand $command): void
268
    {
269
        /** @var IdentityApi $identity */
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...
270
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
271
272
        $identity->verifyEmail($command->verificationNonce);
273
274
        $this->eventSourcedRepository->save($identity);
275
    }
276
277
278
    public function handleProvePhoneRecoveryTokenPossessionCommand(ProvePhoneRecoveryTokenPossessionCommand $command,): void
279
    {
280
        /** @var IdentityApi $identity */
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...
281
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
282
283
        $this->assertSelfAssertedTokensEnabled($identity->getInstitution());
284
        $identity->provePossessionOfPhoneRecoveryToken(
285
            new RecoveryTokenId($command->recoveryTokenId),
286
            new PhoneNumber($command->phoneNumber),
287
        );
288
289
        $this->eventSourcedRepository->save($identity);
290
    }
291
292
    public function handlePromiseSafeStoreSecretTokenPossessionCommand(
293
        PromiseSafeStoreSecretTokenPossessionCommand $command,
294
    ): void {
295
        /** @var IdentityApi $identity */
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...
296
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
297
298
        $this->assertSelfAssertedTokensEnabled($identity->getInstitution());
299
        $secret = $this->recoveryTokenSecretHelper->hash(new UnhashedSecret($command->secret));
300
        $identity->promisePossessionOfSafeStoreSecretRecoveryToken(
301
            new RecoveryTokenId($command->recoveryTokenId),
302
            new SafeStore($secret),
303
        );
304
305
        $this->eventSourcedRepository->save($identity);
306
    }
307
308
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command): void
309
    {
310
        /** @var IdentityApi $authority */
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...
311
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
312
        /** @var IdentityApi $registrant */
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...
313
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
314
315
        $secondFactorType = new SecondFactorType($command->secondFactorType);
316
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
317
            $secondFactorType,
318
            $command->secondFactorIdentifier,
319
        );
320
321
        $authority->vetSecondFactor(
322
            $registrant,
323
            new SecondFactorId($command->secondFactorId),
324
            $secondFactorType,
325
            $secondFactorIdentifier,
326
            $command->registrationCode,
327
            new DocumentNumber($command->documentNumber),
328
            $command->identityVerified,
329
            $this->secondFactorTypeService,
330
            $this->provePossessionHelper,
331
            $command->provePossessionSkipped,
332
        );
333
334
        $this->eventSourcedRepository->save($authority);
335
        $this->eventSourcedRepository->save($registrant);
336
    }
337
338
    public function handleRegisterSelfAssertedSecondFactorCommand(RegisterSelfAssertedSecondFactorCommand $command,): void
339
    {
340
        /** @var IdentityApi $identity */
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...
341
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
342
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
343
            new SecondFactorType($command->secondFactorType),
344
            $command->secondFactorIdentifier,
345
        );
346
347
        $identity->registerSelfAssertedSecondFactor(
348
            $secondFactorIdentifier,
349
            $this->secondFactorTypeService,
350
            new RecoveryTokenId($command->authoringRecoveryTokenId),
351
        );
352
353
        $this->eventSourcedRepository->save($identity);
354
    }
355
356
    public function handleSelfVetSecondFactorCommand(SelfVetSecondFactorCommand $command): void
357
    {
358
        /** @var IdentityApi $identity */
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...
359
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
360
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
361
            new SecondFactorType($command->secondFactorType),
362
            $command->secondFactorId,
363
        );
364
365
        $loaIdentifier = null;
366
        // Be backwards compatible for SelfService 3.5, there we misused the `authoringSecondFactorIdentifier` field
367
        // on the SelfVetSecondFactorCommand to pass along the LoA of the authoring second factor token.
368
        // This was repaired in the SAT release of SelfService (4.0 and upwards)
369
        // the field was renamed to `authoringSecondFactorLoa` then.
370
        //
371
        // @todo remove this BC construct once we drop BC support for SelfService 3.5
372
        if ($command->authoringSecondFactorIdentifier) {
373
            $loaIdentifier = $command->authoringSecondFactorIdentifier;
374
        } elseif ($command->authoringSecondFactorLoa !== '' && $command->authoringSecondFactorLoa !== '0') {
375
            $loaIdentifier = $command->authoringSecondFactorLoa;
376
        }
377
        if (!$loaIdentifier) {
378
            throw new UnknownLoaException('The authoring LoA was not configured on the command');
379
        }
380
381
        $loa = $this->loaResolutionService->getLoa($loaIdentifier);
382
        if ($loa === null) {
383
            throw new UnknownLoaException(
384
                sprintf(
385
                    'Authorizing second factor with LoA %s can not be resolved',
386
                    $command->authoringSecondFactorLoa,
387
                ),
388
            );
389
        }
390
391
        $identity->selfVetSecondFactor(
392
            $loa,
393
            $command->registrationCode,
394
            $secondFactorIdentifier,
395
            $this->secondFactorTypeService,
396
        );
397
        $this->eventSourcedRepository->save($identity);
398
    }
399
400
    public function handleMigrateVettedSecondFactorCommand(MigrateVettedSecondFactorCommand $command): void
401
    {
402
        /** @var IdentityApi $sourceIdentity */
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...
403
        $sourceIdentity = $this->eventSourcedRepository->load(new IdentityId($command->sourceIdentityId));
404
        /** @var IdentityApi $targetIdentity */
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...
405
        $targetIdentity = $this->eventSourcedRepository->load(new IdentityId($command->targetIdentityId));
406
407
        // Check if second factor type is allowed by destination institution
408
        $secondFactor = $sourceIdentity->getVettedSecondFactorById(new SecondFactorId($command->sourceSecondFactorId));
409
        $this->assertSecondFactorIsAllowedFor($secondFactor->getType(), $targetIdentity->getInstitution());
410
411
        // Determine the maximum number of allowed tokens for the institution
412
        $configurationInstitution = new ConfigurationInstitution(
413
            (string)$targetIdentity->getInstitution(),
414
        );
415
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
416
417
        // move second factor
418
        $targetIdentity->migrateVettedSecondFactor(
419
            $sourceIdentity,
420
            new SecondFactorId($command->sourceSecondFactorId),
421
            $command->targetSecondFactorId,
422
            $tokenCount,
423
        );
424
        $this->eventSourcedRepository->save($targetIdentity);
425
    }
426
427
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command): void
428
    {
429
        /** @var IdentityApi $identity */
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...
430
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
431
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
432
433
        $this->eventSourcedRepository->save($identity);
434
    }
435
436
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command): void
437
    {
438
        /** @var IdentityApi $identity */
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...
439
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
440
        $identity->complyWithSecondFactorRevocation(
441
            new SecondFactorId($command->secondFactorId),
442
            new IdentityId($command->authorityId),
443
        );
444
445
        $this->eventSourcedRepository->save($identity);
446
    }
447
448
    public function handleRevokeOwnRecoveryTokenCommand(RevokeOwnRecoveryTokenCommand $command): void
449
    {
450
        /** @var IdentityApi $identity */
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...
451
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
452
        $identity->revokeRecoveryToken(new RecoveryTokenId($command->recoveryTokenId));
453
454
        $this->eventSourcedRepository->save($identity);
455
    }
456
457
    public function handleRevokeRegistrantsRecoveryTokenCommand(RevokeRegistrantsRecoveryTokenCommand $command): void
458
    {
459
        /** @var IdentityApi $identity */
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...
460
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
461
        $identity->complyWithRecoveryTokenRevocation(
462
            new RecoveryTokenId($command->recoveryTokenId),
463
            new IdentityId($command->authorityId),
464
        );
465
466
        $this->eventSourcedRepository->save($identity);
467
    }
468
469
    public function handleSendSecondFactorRegistrationEmailCommand(SendSecondFactorRegistrationEmailCommand $command,): void
470
    {
471
        $this->registrationMailService->send($command->identityId, $command->secondFactorId);
472
    }
473
474
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command): void
475
    {
476
        $preferredLocale = new Locale($command->preferredLocale);
477
        $this->assertIsValidLocale($preferredLocale);
478
479
        /** @var IdentityApi $identity */
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
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
481
        $identity->expressPreferredLocale($preferredLocale);
482
483
        $this->eventSourcedRepository->save($identity);
484
    }
485
486
    private function assertIsValidLocale(Locale $locale): void
0 ignored issues
show
Coding Style introduced by
Private method name "IdentityCommandHandler::assertIsValidLocale" must be prefixed with an underscore
Loading history...
487
    {
488
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
489
            throw new UnsupportedLocaleException(
490
                sprintf('Given locale "%s" is not a supported locale', (string)$locale),
491
            );
492
        }
493
    }
494
495
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution): void
0 ignored issues
show
Coding Style introduced by
Private method name "IdentityCommandHandler::assertSecondFactorIsAllowedFor" must be prefixed with an underscore
Loading history...
496
    {
497
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
498
            new ConfigurationInstitution($institution->getInstitution()),
499
        );
500
501
        if (!$allowedSecondFactorList->allows($secondFactor)) {
502
            throw new SecondFactorNotAllowedException(
503
                sprintf(
504
                    'Institution "%s" does not support second factor "%s"',
505
                    $institution->getInstitution(),
506
                    $secondFactor->getSecondFactorType(),
507
                ),
508
            );
509
        }
510
    }
511
512
    public function assertSelfAssertedTokensEnabled(Institution $institution): void
513
    {
514
        $configurationInstitution = new ConfigurationInstitution(
515
            (string)$institution,
516
        );
517
518
        $institutionConfiguration = $this->institutionConfigurationOptionsService
519
            ->findInstitutionConfigurationOptionsFor($configurationInstitution);
520
        if (!$institutionConfiguration || !$institutionConfiguration->selfAssertedTokensOption->isEnabled()) {
521
            throw new RuntimeException(
522
                sprintf(
523
                    'Registration of self-asserted tokens is not allowed for this institution "%s".',
524
                    (string)$institution,
525
                ),
526
            );
527
        }
528
    }
529
530
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $identity should have a doc-comment as per coding-style.
Loading history...
531
     * @return bool
532
     */
533
    private function emailVerificationIsRequired(IdentityApi $identity): bool
0 ignored issues
show
Coding Style introduced by
Private method name "IdentityCommandHandler::emailVerificationIsRequired" must be prefixed with an underscore
Loading history...
534
    {
535
        $institution = new ConfigurationInstitution(
536
            (string)$identity->getInstitution(),
537
        );
538
539
        $configuration = $this->institutionConfigurationOptionsService
540
            ->findInstitutionConfigurationOptionsFor($institution);
541
542
        if (!$configuration instanceof \Surfnet\StepupMiddleware\ApiBundle\Configuration\Entity\InstitutionConfigurationOptions) {
543
            return true;
544
        }
545
546
        return $configuration->verifyEmailOption->isEnabled();
547
    }
548
}
549