Completed
Push — feature/fga-ra-management ( 037bdc...e11d53 )
by
unknown
16:22
created

loadInstitutionConfigurationFor()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 17

Duplication

Lines 17
Ratio 100 %

Importance

Changes 0
Metric Value
dl 17
loc 17
rs 9.7
c 0
b 0
f 0
cc 2
nc 3
nop 1
1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler;
20
21
use Broadway\CommandHandling\CommandHandler;
22
use Broadway\Repository\AggregateNotFoundException;
23
use Broadway\Repository\RepositoryInterface;
24
use Surfnet\Stepup\Configuration\EventSourcing\InstitutionConfigurationRepository;
25
use Surfnet\Stepup\Configuration\InstitutionConfiguration;
26
use Surfnet\Stepup\Configuration\Value\Institution as ConfigurationInstitution;
27
use Surfnet\Stepup\Configuration\Value\InstitutionConfigurationId;
28
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
29
use Surfnet\Stepup\Identity\Entity\ConfigurableSettings;
30
use Surfnet\Stepup\Identity\Identity;
31
use Surfnet\Stepup\Identity\Value\CommonName;
32
use Surfnet\Stepup\Identity\Value\DocumentNumber;
33
use Surfnet\Stepup\Identity\Value\Email;
34
use Surfnet\Stepup\Identity\Value\GssfId;
35
use Surfnet\Stepup\Identity\Value\IdentityId;
36
use Surfnet\Stepup\Identity\Value\Institution;
37
use Surfnet\Stepup\Identity\Value\Locale;
38
use Surfnet\Stepup\Identity\Value\NameId;
39
use Surfnet\Stepup\Identity\Value\PhoneNumber;
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\YubikeyPublicId;
45
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
46
use Surfnet\StepupBundle\Value\SecondFactorType;
47
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
48
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
49
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
50
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
51
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
56
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
57
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveU2fDevicePossessionCommand;
58
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveYubikeyPossessionCommand;
59
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
60
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
61
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
62
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
63
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
64
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler\Exception\DuplicateIdentityException;
65
66
/**
67
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
68
 * @SuppressWarnings(PHPMD.TooManyMethods)
69
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
70
 */
71
class IdentityCommandHandler extends CommandHandler
72
{
73
    /**
74
     * @var \Surfnet\Stepup\Identity\EventSourcing\IdentityRepository
75
     */
76
    private $eventSourcedRepository;
77
78
    /**
79
     * @var IdentityRepository
80
     */
81
    private $identityProjectionRepository;
82
83
    /**
84
     * @var \Surfnet\Stepup\Identity\Entity\ConfigurableSettings
85
     */
86
    private $configurableSettings;
87
88
    /**
89
     * @var AllowedSecondFactorListService
90
     */
91
    private $allowedSecondFactorListService;
92
93
    /** @var SecondFactorTypeService */
94
    private $secondFactorTypeService;
95
96
    /**
97
     * @var InstitutionConfigurationOptionsService
98
     */
99
    private $institutionConfigurationOptionsService;
100
101
    /**
102
     * @var InstitutionConfigurationRepository
103
     */
104
    private $institutionConfigurationRepository;
105
106
    /**
107
     * @param RepositoryInterface $eventSourcedRepository
108
     * @param IdentityRepository $identityProjectionRepository
109
     * @param ConfigurableSettings $configurableSettings
110
     * @param AllowedSecondFactorListService $allowedSecondFactorListService
111
     * @param SecondFactorTypeService $secondFactorTypeService
112
     * @param InstitutionConfigurationOptionsService $institutionConfigurationOptionsService
113
     * @param InstitutionConfigurationRepository $institutionConfigurationRepository
114
     */
115
    public function __construct(
116
        RepositoryInterface $eventSourcedRepository,
117
        IdentityRepository $identityProjectionRepository,
118
        ConfigurableSettings $configurableSettings,
119
        AllowedSecondFactorListService $allowedSecondFactorListService,
120
        SecondFactorTypeService $secondFactorTypeService,
121
        InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
122
        InstitutionConfigurationRepository $institutionConfigurationRepository
123
    ) {
124
        $this->eventSourcedRepository = $eventSourcedRepository;
0 ignored issues
show
Documentation Bug introduced by
$eventSourcedRepository is of type object<Broadway\Repository\RepositoryInterface>, but the property $eventSourcedRepository was declared to be of type object<Surfnet\Stepup\Id...ing\IdentityRepository>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
125
        $this->identityProjectionRepository = $identityProjectionRepository;
126
        $this->configurableSettings = $configurableSettings;
127
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
128
        $this->secondFactorTypeService = $secondFactorTypeService;
129
        $this->institutionConfigurationOptionsService = $institutionConfigurationOptionsService;
130
        $this->institutionConfigurationRepository = $institutionConfigurationRepository;
131
    }
132
133
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
134
    {
135
        $preferredLocale = new Locale($command->preferredLocale);
136
        $this->assertIsValidLocale($preferredLocale);
137
138
        $institution = new Institution($command->institution);
139
140
        $institutionConfiguration = $this->loadInstitutionConfigurationFor($institution);
0 ignored issues
show
Deprecated Code introduced by
The method Surfnet\StepupMiddleware...utionConfigurationFor() has been deprecated with message: Should be used until existing institution configurations have been migrated to using normalized ids

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

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

Loading history...
141
142
        $identity = Identity::create(
143
            new IdentityId($command->id),
144
            new Institution($command->institution),
145
            new NameId($command->nameId),
146
            new CommonName($command->commonName),
147
            new Email($command->email),
148
            $preferredLocale,
149
            $institutionConfiguration
0 ignored issues
show
Unused Code introduced by
The call to Identity::create() has too many arguments starting with $institutionConfiguration.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
150
        );
151
152
        $this->eventSourcedRepository->save($identity);
153
    }
154
155
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command)
156
    {
157
        /** @var IdentityApi $identity */
158
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->id));
159
160
        $identity->rename(new CommonName($command->commonName));
161
        $identity->changeEmail(new Email($command->email));
162
163
        $this->eventSourcedRepository->save($identity);
164
    }
165
166
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
167
        BootstrapIdentityWithYubikeySecondFactorCommand $command
168
    ) {
169
        $preferredLocale = new Locale($command->preferredLocale);
170
        $this->assertIsValidLocale($preferredLocale);
171
172
        $institution = new Institution($command->institution);
173
        $nameId = new NameId($command->nameId);
174
175
        if ($this->identityProjectionRepository->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
176
            throw DuplicateIdentityException::forBootstrappingWithYubikeySecondFactor($nameId, $institution);
177
        }
178
179
        $institutionConfiguration = $this->loadInstitutionConfigurationFor($institution);
0 ignored issues
show
Deprecated Code introduced by
The method Surfnet\StepupMiddleware...utionConfigurationFor() has been deprecated with message: Should be used until existing institution configurations have been migrated to using normalized ids

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

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

Loading history...
180
181
        $identity = Identity::create(
182
            new IdentityId($command->identityId),
183
            $institution,
184
            $nameId,
185
            new CommonName($command->commonName),
186
            new Email($command->email),
187
            $preferredLocale,
188
            $institutionConfiguration
0 ignored issues
show
Unused Code introduced by
The call to Identity::create() has too many arguments starting with $institutionConfiguration.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
189
        );
190
191
        $configurationInstitution = new ConfigurationInstitution(
192
            (string) $identity->getInstitution()
193
        );
194
195
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
196
        $identity->setMaxNumberOfTokens($tokenCount);
197
198
        $identity->bootstrapYubikeySecondFactor(
199
            new SecondFactorId($command->secondFactorId),
200
            new YubikeyPublicId($command->yubikeyPublicId)
201
        );
202
203
        $this->eventSourcedRepository->save($identity);
204
    }
205
206 View Code Duplication
    public function handleProveYubikeyPossessionCommand(ProveYubikeyPossessionCommand $command)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
207
    {
208
        /** @var IdentityApi $identity */
209
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
210
211
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('yubikey'), $identity->getInstitution());
212
213
        $configurationInstitution = new ConfigurationInstitution(
214
            (string) $identity->getInstitution()
215
        );
216
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
217
        $identity->setMaxNumberOfTokens($tokenCount);
218
219
        $identity->provePossessionOfYubikey(
220
            new SecondFactorId($command->secondFactorId),
221
            new YubikeyPublicId($command->yubikeyPublicId),
222
            $this->emailVerificationIsRequired($identity),
223
            $this->configurableSettings->createNewEmailVerificationWindow()
224
        );
225
226
        $this->eventSourcedRepository->save($identity);
227
    }
228
229
    /**
230
     * @param ProvePhonePossessionCommand $command
231
     */
232 View Code Duplication
    public function handleProvePhonePossessionCommand(ProvePhonePossessionCommand $command)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
233
    {
234
        /** @var IdentityApi $identity */
235
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
236
237
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('sms'), $identity->getInstitution());
238
239
        $configurationInstitution = new ConfigurationInstitution(
240
            (string) $identity->getInstitution()
241
        );
242
243
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
244
        $identity->setMaxNumberOfTokens($tokenCount);
245
246
        $identity->provePossessionOfPhone(
247
            new SecondFactorId($command->secondFactorId),
248
            new PhoneNumber($command->phoneNumber),
249
            $this->emailVerificationIsRequired($identity),
250
            $this->configurableSettings->createNewEmailVerificationWindow()
251
        );
252
253
        $this->eventSourcedRepository->save($identity);
254
    }
255
256
    /**
257
     * @param ProveGssfPossessionCommand $command
258
     */
259
    public function handleProveGssfPossessionCommand(ProveGssfPossessionCommand $command)
260
    {
261
        /** @var IdentityApi $identity */
262
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
263
        $secondFactorType = $command->stepupProvider;
264
265
        // Validate that the chosen second factor type (stepupProvider) is allowed for the users instituti
266
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType($secondFactorType), $identity->getInstitution());
267
268
        $configurationInstitution = new ConfigurationInstitution(
269
            (string) $identity->getInstitution()
270
        );
271
272
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
273
        $identity->setMaxNumberOfTokens($tokenCount);
274
275
        $identity->provePossessionOfGssf(
276
            new SecondFactorId($command->secondFactorId),
277
            new StepupProvider($secondFactorType),
278
            new GssfId($command->gssfId),
279
            $this->emailVerificationIsRequired($identity),
280
            $this->configurableSettings->createNewEmailVerificationWindow()
281
        );
282
283
        $this->eventSourcedRepository->save($identity);
284
    }
285
286 View Code Duplication
    public function handleProveU2fDevicePossessionCommand(ProveU2fDevicePossessionCommand $command)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
287
    {
288
        /** @var IdentityApi $identity */
289
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
290
291
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
292
293
        $configurationInstitution = new ConfigurationInstitution(
294
            (string) $identity->getInstitution()
295
        );
296
297
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
298
        $identity->setMaxNumberOfTokens($tokenCount);
299
300
        $identity->provePossessionOfU2fDevice(
301
            new SecondFactorId($command->secondFactorId),
302
            new U2fKeyHandle($command->keyHandle),
303
            $this->emailVerificationIsRequired($identity),
304
            $this->configurableSettings->createNewEmailVerificationWindow()
305
        );
306
307
        $this->eventSourcedRepository->save($identity);
308
    }
309
310
    /**
311
     * @param VerifyEmailCommand $command
312
     */
313
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
314
    {
315
        /** @var IdentityApi $identity */
316
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
317
318
        $identity->verifyEmail($command->verificationNonce);
319
320
        $this->eventSourcedRepository->save($identity);
321
    }
322
323
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
324
    {
325
        /** @var IdentityApi $authority */
326
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
327
        /** @var IdentityApi $registrant */
328
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
329
330
        $secondFactorType = new SecondFactorType($command->secondFactorType);
331
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
332
            $secondFactorType,
333
            $command->secondFactorIdentifier
334
        );
335
336
        $authority->vetSecondFactor(
337
            $registrant,
338
            new SecondFactorId($command->secondFactorId),
339
            $secondFactorType,
340
            $secondFactorIdentifier,
341
            $command->registrationCode,
342
            new DocumentNumber($command->documentNumber),
343
            $command->identityVerified,
344
            $this->secondFactorTypeService
345
        );
346
347
        $this->eventSourcedRepository->save($authority);
348
        $this->eventSourcedRepository->save($registrant);
349
    }
350
351
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
352
    {
353
        /** @var IdentityApi $identity */
354
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
355
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
356
357
        $this->eventSourcedRepository->save($identity);
358
    }
359
360
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
361
    {
362
        /** @var IdentityApi $identity */
363
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
364
        $identity->complyWithSecondFactorRevocation(
365
            new SecondFactorId($command->secondFactorId),
366
            new IdentityId($command->authorityId)
367
        );
368
369
        $this->eventSourcedRepository->save($identity);
370
    }
371
372
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
373
    {
374
        $preferredLocale = new Locale($command->preferredLocale);
375
        $this->assertIsValidLocale($preferredLocale);
376
377
        /** @var IdentityApi $identity */
378
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
379
        $identity->expressPreferredLocale($preferredLocale);
380
381
        $this->eventSourcedRepository->save($identity);
382
    }
383
384
    /**
385
     * @param Locale $locale
386
     */
387
    private function assertIsValidLocale(Locale $locale)
388
    {
389
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
390
            throw new UnsupportedLocaleException(
391
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
392
            );
393
        }
394
    }
395
396
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
397
    {
398
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
399
            new ConfigurationInstitution($institution->getInstitution())
400
        );
401
402
        if (!$allowedSecondFactorList->allows($secondFactor)) {
403
            throw new SecondFactorNotAllowedException(sprintf(
404
                'Institution "%s" does not support second factor "%s"',
405
                $institution->getInstitution(),
406
                $secondFactor->getSecondFactorType()
407
            ));
408
        }
409
    }
410
411
    /**
412
     * @param IdentityApi $identity
413
     * @return bool
414
     */
415
    private function emailVerificationIsRequired(IdentityApi $identity)
416
    {
417
        $institution = new ConfigurationInstitution(
418
            (string) $identity->getInstitution()
419
        );
420
421
        $configuration = $this->institutionConfigurationOptionsService
422
            ->findInstitutionConfigurationOptionsFor($institution);
423
424
        if ($configuration === null) {
425
            return true;
426
        }
427
428
        return $configuration->verifyEmailOption->isEnabled();
429
    }
430
431
    /**
432
     * @deprecated Should be used until existing institution configurations have been migrated to using normalized ids
433
     *
434
     * @param Institution $institution
435
     * @return InstitutionConfiguration
436
     */
437 View Code Duplication
    private function loadInstitutionConfigurationFor(Institution $institution)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
438
    {
439
        $institution = new ConfigurationInstitution($institution->getInstitution());
440
        try {
441
            $institutionConfigurationId = InstitutionConfigurationId::normalizedFrom($institution);
442
            $institutionConfiguration = $this->institutionConfigurationRepository->load(
443
                $institutionConfigurationId->getInstitutionConfigurationId()
444
            );
445
        } catch (AggregateNotFoundException $exception) {
446
            $institutionConfigurationId = InstitutionConfigurationId::from($institution);
0 ignored issues
show
Deprecated Code introduced by
The method Surfnet\Stepup\Configura...ConfigurationId::from() has been deprecated with message: To be removed in next release; use normalizedFrom method to account for case-(in)sensitivity issues

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

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

Loading history...
447
            $institutionConfiguration = $this->institutionConfigurationRepository->load(
448
                $institutionConfigurationId->getInstitutionConfigurationId()
449
            );
450
        }
451
452
        return $institutionConfiguration;
453
    }
454
}
455