Completed
Pull Request — develop (#285)
by
unknown
04:32 queued 02:13
created

handleProveU2fDevicePossessionCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23

Duplication

Lines 23
Ratio 100 %

Importance

Changes 0
Metric Value
dl 23
loc 23
rs 9.552
c 0
b 0
f 0
cc 1
nc 1
nop 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A IdentityCommandHandler::handleVerifyEmailCommand() 0 9 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\YubikeyPublicId;
44
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
45
use Surfnet\StepupBundle\Value\SecondFactorType;
46
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
47
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
48
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
49
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
50
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
51
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
56
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveYubikeyPossessionCommand;
57
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
58
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
59
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
60
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
61
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
62
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler\Exception\DuplicateIdentityException;
63
64
/**
65
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
66
 * @SuppressWarnings(PHPMD.TooManyMethods)
67
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
68
 */
69
class IdentityCommandHandler extends CommandHandler
70
{
71
    /**
72
     * @var \Surfnet\Stepup\Identity\EventSourcing\IdentityRepository
73
     */
74
    private $eventSourcedRepository;
75
76
    /**
77
     * @var IdentityRepository
78
     */
79
    private $identityProjectionRepository;
80
81
    /**
82
     * @var \Surfnet\Stepup\Identity\Entity\ConfigurableSettings
83
     */
84
    private $configurableSettings;
85
86
    /**
87
     * @var AllowedSecondFactorListService
88
     */
89
    private $allowedSecondFactorListService;
90
91
    /** @var SecondFactorTypeService */
92
    private $secondFactorTypeService;
93
94
    /**
95
     * @var InstitutionConfigurationOptionsService
96
     */
97
    private $institutionConfigurationOptionsService;
98
99
    /**
100
     * @var InstitutionConfigurationRepository
101
     */
102
    private $institutionConfigurationRepository;
103
104
    /**
105
     * @param RepositoryInterface $eventSourcedRepository
106
     * @param IdentityRepository $identityProjectionRepository
107
     * @param ConfigurableSettings $configurableSettings
108
     * @param AllowedSecondFactorListService $allowedSecondFactorListService
109
     * @param SecondFactorTypeService $secondFactorTypeService
110
     * @param InstitutionConfigurationOptionsService $institutionConfigurationOptionsService
111
     * @param InstitutionConfigurationRepository $institutionConfigurationRepository
112
     */
113
    public function __construct(
114
        RepositoryInterface $eventSourcedRepository,
115
        IdentityRepository $identityProjectionRepository,
116
        ConfigurableSettings $configurableSettings,
117
        AllowedSecondFactorListService $allowedSecondFactorListService,
118
        SecondFactorTypeService $secondFactorTypeService,
119
        InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
120
        InstitutionConfigurationRepository $institutionConfigurationRepository
121
    ) {
122
        $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...
123
        $this->identityProjectionRepository = $identityProjectionRepository;
124
        $this->configurableSettings = $configurableSettings;
125
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
126
        $this->secondFactorTypeService = $secondFactorTypeService;
127
        $this->institutionConfigurationOptionsService = $institutionConfigurationOptionsService;
128
        $this->institutionConfigurationRepository = $institutionConfigurationRepository;
129
    }
130
131
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
132
    {
133
        $preferredLocale = new Locale($command->preferredLocale);
134
        $this->assertIsValidLocale($preferredLocale);
135
136
        $institution = new Institution($command->institution);
137
138
        $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...
139
140
        $identity = Identity::create(
141
            new IdentityId($command->id),
142
            new Institution($command->institution),
143
            new NameId($command->nameId),
144
            new CommonName($command->commonName),
145
            new Email($command->email),
146
            $preferredLocale,
147
            $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...
148
        );
149
150
        $this->eventSourcedRepository->save($identity);
151
    }
152
153
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command)
154
    {
155
        /** @var IdentityApi $identity */
156
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->id));
157
158
        $identity->rename(new CommonName($command->commonName));
159
        $identity->changeEmail(new Email($command->email));
160
161
        $this->eventSourcedRepository->save($identity);
162
    }
163
164
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
165
        BootstrapIdentityWithYubikeySecondFactorCommand $command
166
    ) {
167
        $preferredLocale = new Locale($command->preferredLocale);
168
        $this->assertIsValidLocale($preferredLocale);
169
170
        $institution = new Institution($command->institution);
171
        $nameId = new NameId($command->nameId);
172
173
        if ($this->identityProjectionRepository->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
174
            throw DuplicateIdentityException::forBootstrappingWithYubikeySecondFactor($nameId, $institution);
175
        }
176
177
        $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...
178
179
        $identity = Identity::create(
180
            new IdentityId($command->identityId),
181
            $institution,
182
            $nameId,
183
            new CommonName($command->commonName),
184
            new Email($command->email),
185
            $preferredLocale,
186
            $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...
187
        );
188
189
        $configurationInstitution = new ConfigurationInstitution(
190
            (string) $identity->getInstitution()
191
        );
192
193
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
194
        $identity->setMaxNumberOfTokens($tokenCount);
195
196
        $identity->bootstrapYubikeySecondFactor(
197
            new SecondFactorId($command->secondFactorId),
198
            new YubikeyPublicId($command->yubikeyPublicId)
199
        );
200
201
        $this->eventSourcedRepository->save($identity);
202
    }
203
204 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...
205
    {
206
        /** @var IdentityApi $identity */
207
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
208
209
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('yubikey'), $identity->getInstitution());
210
211
        $configurationInstitution = new ConfigurationInstitution(
212
            (string) $identity->getInstitution()
213
        );
214
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
215
        $identity->setMaxNumberOfTokens($tokenCount);
216
217
        $identity->provePossessionOfYubikey(
218
            new SecondFactorId($command->secondFactorId),
219
            new YubikeyPublicId($command->yubikeyPublicId),
220
            $this->emailVerificationIsRequired($identity),
221
            $this->configurableSettings->createNewEmailVerificationWindow()
222
        );
223
224
        $this->eventSourcedRepository->save($identity);
225
    }
226
227
    /**
228
     * @param ProvePhonePossessionCommand $command
229
     */
230 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...
231
    {
232
        /** @var IdentityApi $identity */
233
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
234
235
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('sms'), $identity->getInstitution());
236
237
        $configurationInstitution = new ConfigurationInstitution(
238
            (string) $identity->getInstitution()
239
        );
240
241
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
242
        $identity->setMaxNumberOfTokens($tokenCount);
243
244
        $identity->provePossessionOfPhone(
245
            new SecondFactorId($command->secondFactorId),
246
            new PhoneNumber($command->phoneNumber),
247
            $this->emailVerificationIsRequired($identity),
248
            $this->configurableSettings->createNewEmailVerificationWindow()
249
        );
250
251
        $this->eventSourcedRepository->save($identity);
252
    }
253
254
    /**
255
     * @param ProveGssfPossessionCommand $command
256
     */
257
    public function handleProveGssfPossessionCommand(ProveGssfPossessionCommand $command)
258
    {
259
        /** @var IdentityApi $identity */
260
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
261
        $secondFactorType = $command->stepupProvider;
262
263
        // Validate that the chosen second factor type (stepupProvider) is allowed for the users instituti
264
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType($secondFactorType), $identity->getInstitution());
265
266
        $configurationInstitution = new ConfigurationInstitution(
267
            (string) $identity->getInstitution()
268
        );
269
270
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
271
        $identity->setMaxNumberOfTokens($tokenCount);
272
273
        $identity->provePossessionOfGssf(
274
            new SecondFactorId($command->secondFactorId),
275
            new StepupProvider($secondFactorType),
276
            new GssfId($command->gssfId),
277
            $this->emailVerificationIsRequired($identity),
278
            $this->configurableSettings->createNewEmailVerificationWindow()
279
        );
280
281
        $this->eventSourcedRepository->save($identity);
282
    }
283
284
    /**
285
     * @param VerifyEmailCommand $command
286
     */
287
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
288
    {
289
        /** @var IdentityApi $identity */
290
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
291
292
        $identity->verifyEmail($command->verificationNonce);
293
294
        $this->eventSourcedRepository->save($identity);
295
    }
296
297
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
298
    {
299
        /** @var IdentityApi $authority */
300
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
301
        /** @var IdentityApi $registrant */
302
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
303
304
        $secondFactorType = new SecondFactorType($command->secondFactorType);
305
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
306
            $secondFactorType,
307
            $command->secondFactorIdentifier
308
        );
309
310
        $authority->vetSecondFactor(
311
            $registrant,
312
            new SecondFactorId($command->secondFactorId),
313
            $secondFactorType,
314
            $secondFactorIdentifier,
315
            $command->registrationCode,
316
            new DocumentNumber($command->documentNumber),
317
            $command->identityVerified,
318
            $this->secondFactorTypeService
319
        );
320
321
        $this->eventSourcedRepository->save($authority);
322
        $this->eventSourcedRepository->save($registrant);
323
    }
324
325
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
326
    {
327
        /** @var IdentityApi $identity */
328
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
329
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
330
331
        $this->eventSourcedRepository->save($identity);
332
    }
333
334
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
335
    {
336
        /** @var IdentityApi $identity */
337
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
338
        $identity->complyWithSecondFactorRevocation(
339
            new SecondFactorId($command->secondFactorId),
340
            new IdentityId($command->authorityId)
341
        );
342
343
        $this->eventSourcedRepository->save($identity);
344
    }
345
346
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
347
    {
348
        $preferredLocale = new Locale($command->preferredLocale);
349
        $this->assertIsValidLocale($preferredLocale);
350
351
        /** @var IdentityApi $identity */
352
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
353
        $identity->expressPreferredLocale($preferredLocale);
354
355
        $this->eventSourcedRepository->save($identity);
356
    }
357
358
    /**
359
     * @param Locale $locale
360
     */
361
    private function assertIsValidLocale(Locale $locale)
362
    {
363
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
364
            throw new UnsupportedLocaleException(
365
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
366
            );
367
        }
368
    }
369
370
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
371
    {
372
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
373
            new ConfigurationInstitution($institution->getInstitution())
374
        );
375
376
        if (!$allowedSecondFactorList->allows($secondFactor)) {
377
            throw new SecondFactorNotAllowedException(sprintf(
378
                'Institution "%s" does not support second factor "%s"',
379
                $institution->getInstitution(),
380
                $secondFactor->getSecondFactorType()
381
            ));
382
        }
383
    }
384
385
    /**
386
     * @param IdentityApi $identity
387
     * @return bool
388
     */
389
    private function emailVerificationIsRequired(IdentityApi $identity)
390
    {
391
        $institution = new ConfigurationInstitution(
392
            (string) $identity->getInstitution()
393
        );
394
395
        $configuration = $this->institutionConfigurationOptionsService
396
            ->findInstitutionConfigurationOptionsFor($institution);
397
398
        if ($configuration === null) {
399
            return true;
400
        }
401
402
        return $configuration->verifyEmailOption->isEnabled();
403
    }
404
405
    /**
406
     * @deprecated Should be used until existing institution configurations have been migrated to using normalized ids
407
     *
408
     * @param Institution $institution
409
     * @return InstitutionConfiguration
410
     */
411 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...
412
    {
413
        $institution = new ConfigurationInstitution($institution->getInstitution());
414
        try {
415
            $institutionConfigurationId = InstitutionConfigurationId::normalizedFrom($institution);
416
            $institutionConfiguration = $this->institutionConfigurationRepository->load(
417
                $institutionConfigurationId->getInstitutionConfigurationId()
418
            );
419
        } catch (AggregateNotFoundException $exception) {
420
            $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...
421
            $institutionConfiguration = $this->institutionConfigurationRepository->load(
422
                $institutionConfigurationId->getInstitutionConfigurationId()
423
            );
424
        }
425
426
        return $institutionConfiguration;
427
    }
428
}
429