Completed
Pull Request — develop (#320)
by Michiel
04:42
created

IdentityCommandHandler   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 378
Duplicated Lines 17.99 %

Coupling/Cohesion

Components 1
Dependencies 43

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 43
dl 68
loc 378
rs 10
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A handleProveYubikeyPossessionCommand() 22 22 1
A handleProvePhonePossessionCommand() 23 23 1
A handleProveGssfPossessionCommand() 0 26 1
A handleUpdateIdentityCommand() 0 10 1
A __construct() 0 19 1
A handleCreateIdentityCommand() 0 16 1
A handleBootstrapIdentityWithYubikeySecondFactorCommand() 0 36 2
A handleProveU2fDevicePossessionCommand() 23 23 1
A handleVerifyEmailCommand() 0 9 1
A handleVetSecondFactorCommand() 0 29 1
A handleSelfVetSecondFactorCommand() 0 16 1
A handleRevokeOwnSecondFactorCommand() 0 8 1
A handleRevokeRegistrantsSecondFactorCommand() 0 11 1
A handleExpressLocalePreferenceCommand() 0 11 1
A assertIsValidLocale() 0 8 2
A assertSecondFactorIsAllowedFor() 0 14 2
A emailVerificationIsRequired() 0 15 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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\SimpleCommandHandler;
22
use Broadway\Repository\Repository as RepositoryInterface;
23
use Surfnet\Stepup\Configuration\EventSourcing\InstitutionConfigurationRepository;
24
use Surfnet\Stepup\Configuration\Value\Institution as ConfigurationInstitution;
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;
36
use Surfnet\Stepup\Identity\Value\NameId;
37
use Surfnet\Stepup\Identity\Value\PhoneNumber;
38
use Surfnet\Stepup\Identity\Value\SecondFactorId;
39
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifierFactory;
40
use Surfnet\Stepup\Identity\Value\StepupProvider;
41
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
42
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
43
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
44
use Surfnet\StepupBundle\Value\SecondFactorType;
45
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
46
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
47
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
48
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
49
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
50
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
51
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveU2fDevicePossessionCommand;
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\SelfVetSecondFactorCommand;
60
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
61
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
62
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
63
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler\Exception\DuplicateIdentityException;
64
65
/**
66
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
67
 * @SuppressWarnings(PHPMD.TooManyMethods)
68
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
69
 */
70
class IdentityCommandHandler extends SimpleCommandHandler
71
{
72
    /**
73
     * @var \Surfnet\Stepup\Identity\EventSourcing\IdentityRepository
74
     */
75
    private $eventSourcedRepository;
76
77
    /**
78
     * @var IdentityRepository
79
     */
80
    private $identityProjectionRepository;
81
82
    /**
83
     * @var \Surfnet\Stepup\Identity\Entity\ConfigurableSettings
84
     */
85
    private $configurableSettings;
86
87
    /**
88
     * @var AllowedSecondFactorListService
89
     */
90
    private $allowedSecondFactorListService;
91
92
    /** @var SecondFactorTypeService */
93
    private $secondFactorTypeService;
94
95
    /**
96
     * @var InstitutionConfigurationOptionsService
97
     */
98
    private $institutionConfigurationOptionsService;
99
100
    /**
101
     * @var InstitutionConfigurationRepository
102
     */
103
    private $institutionConfigurationRepository;
104
    /**
105
     * @var SecondFactorProvePossessionHelper
106
     */
107
    private $provePossessionHelper;
108
109
    /**
110
     * @param RepositoryInterface $eventSourcedRepository
111
     * @param IdentityRepository $identityProjectionRepository
112
     * @param ConfigurableSettings $configurableSettings
113
     * @param AllowedSecondFactorListService $allowedSecondFactorListService
114
     * @param SecondFactorTypeService $secondFactorTypeService
115
     * @param SecondFactorProvePossessionHelper $provePossessionHelper
116
     * @param InstitutionConfigurationOptionsService $institutionConfigurationOptionsService
117
     * @param InstitutionConfigurationRepository $institutionConfigurationRepository
118
     */
119
    public function __construct(
120
        RepositoryInterface $eventSourcedRepository,
121
        IdentityRepository $identityProjectionRepository,
122
        ConfigurableSettings $configurableSettings,
123
        AllowedSecondFactorListService $allowedSecondFactorListService,
124
        SecondFactorTypeService $secondFactorTypeService,
125
        SecondFactorProvePossessionHelper $provePossessionHelper,
126
        InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
127
        InstitutionConfigurationRepository $institutionConfigurationRepository
128
    ) {
129
        $this->eventSourcedRepository = $eventSourcedRepository;
0 ignored issues
show
Documentation Bug introduced by
$eventSourcedRepository is of type object<Broadway\Repository\Repository>, 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...
130
        $this->identityProjectionRepository = $identityProjectionRepository;
131
        $this->configurableSettings = $configurableSettings;
132
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
133
        $this->secondFactorTypeService = $secondFactorTypeService;
134
        $this->provePossessionHelper = $provePossessionHelper;
135
        $this->institutionConfigurationOptionsService = $institutionConfigurationOptionsService;
136
        $this->institutionConfigurationRepository = $institutionConfigurationRepository;
137
    }
138
139
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
140
    {
141
        $preferredLocale = new Locale($command->preferredLocale);
142
        $this->assertIsValidLocale($preferredLocale);
143
144
        $identity = Identity::create(
145
            new IdentityId($command->id),
146
            new Institution($command->institution),
147
            new NameId($command->nameId),
148
            new CommonName($command->commonName),
149
            new Email($command->email),
150
            $preferredLocale
151
        );
152
153
        $this->eventSourcedRepository->save($identity);
154
    }
155
156
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command)
157
    {
158
        /** @var IdentityApi $identity */
159
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->id));
160
161
        $identity->rename(new CommonName($command->commonName));
162
        $identity->changeEmail(new Email($command->email));
163
164
        $this->eventSourcedRepository->save($identity);
165
    }
166
167
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
168
        BootstrapIdentityWithYubikeySecondFactorCommand $command
169
    ) {
170
        $preferredLocale = new Locale($command->preferredLocale);
171
        $this->assertIsValidLocale($preferredLocale);
172
173
        $institution = new Institution($command->institution);
174
        $nameId = new NameId($command->nameId);
175
176
        if ($this->identityProjectionRepository->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
177
            throw DuplicateIdentityException::forBootstrappingWithYubikeySecondFactor($nameId, $institution);
178
        }
179
180
        $identity = Identity::create(
181
            new IdentityId($command->identityId),
182
            $institution,
183
            $nameId,
184
            new CommonName($command->commonName),
185
            new Email($command->email),
186
            $preferredLocale
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 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...
285
    {
286
        /** @var IdentityApi $identity */
287
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
288
289
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
290
291
        $configurationInstitution = new ConfigurationInstitution(
292
            (string) $identity->getInstitution()
293
        );
294
295
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
296
        $identity->setMaxNumberOfTokens($tokenCount);
297
298
        $identity->provePossessionOfU2fDevice(
299
            new SecondFactorId($command->secondFactorId),
300
            new U2fKeyHandle($command->keyHandle),
301
            $this->emailVerificationIsRequired($identity),
302
            $this->configurableSettings->createNewEmailVerificationWindow()
303
        );
304
305
        $this->eventSourcedRepository->save($identity);
306
    }
307
308
    /**
309
     * @param VerifyEmailCommand $command
310
     */
311
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
312
    {
313
        /** @var IdentityApi $identity */
314
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
315
316
        $identity->verifyEmail($command->verificationNonce);
317
318
        $this->eventSourcedRepository->save($identity);
319
    }
320
321
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
322
    {
323
        /** @var IdentityApi $authority */
324
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
325
        /** @var IdentityApi $registrant */
326
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
327
328
        $secondFactorType = new SecondFactorType($command->secondFactorType);
329
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
330
            $secondFactorType,
331
            $command->secondFactorIdentifier
332
        );
333
334
        $authority->vetSecondFactor(
335
            $registrant,
336
            new SecondFactorId($command->secondFactorId),
337
            $secondFactorType,
338
            $secondFactorIdentifier,
339
            $command->registrationCode,
340
            new DocumentNumber($command->documentNumber),
341
            $command->identityVerified,
342
            $this->secondFactorTypeService,
343
            $this->provePossessionHelper,
344
            $command->provePossessionSkipped
345
        );
346
347
        $this->eventSourcedRepository->save($authority);
348
        $this->eventSourcedRepository->save($registrant);
349
    }
350
351
    public function handleSelfVetSecondFactorCommand(SelfVetSecondFactorCommand $command)
352
    {
353
        /** @var IdentityApi $identity */
354
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
355
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
356
            new SecondFactorType($command->secondFactorType),
357
            $command->secondFactorId
358
        );
359
        $identity->selfVetSecondFactor(
0 ignored issues
show
Bug introduced by
The method selfVetSecondFactor() does not exist on Surfnet\Stepup\Identity\Api\Identity. Did you maybe mean vetSecondFactor()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
360
            new SecondFactorId($command->authoringSecondFactorIdentifier),
361
            $command->registrationCode,
362
            $secondFactorIdentifier,
363
            $this->secondFactorTypeService
364
        );
365
        $this->eventSourcedRepository->save($identity);
366
    }
367
368
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
369
    {
370
        /** @var IdentityApi $identity */
371
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
372
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
373
374
        $this->eventSourcedRepository->save($identity);
375
    }
376
377
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
378
    {
379
        /** @var IdentityApi $identity */
380
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
381
        $identity->complyWithSecondFactorRevocation(
382
            new SecondFactorId($command->secondFactorId),
383
            new IdentityId($command->authorityId)
384
        );
385
386
        $this->eventSourcedRepository->save($identity);
387
    }
388
389
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
390
    {
391
        $preferredLocale = new Locale($command->preferredLocale);
392
        $this->assertIsValidLocale($preferredLocale);
393
394
        /** @var IdentityApi $identity */
395
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
396
        $identity->expressPreferredLocale($preferredLocale);
397
398
        $this->eventSourcedRepository->save($identity);
399
    }
400
401
    /**
402
     * @param Locale $locale
403
     */
404
    private function assertIsValidLocale(Locale $locale)
405
    {
406
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
407
            throw new UnsupportedLocaleException(
408
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
409
            );
410
        }
411
    }
412
413
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
414
    {
415
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
416
            new ConfigurationInstitution($institution->getInstitution())
417
        );
418
419
        if (!$allowedSecondFactorList->allows($secondFactor)) {
420
            throw new SecondFactorNotAllowedException(sprintf(
421
                'Institution "%s" does not support second factor "%s"',
422
                $institution->getInstitution(),
423
                $secondFactor->getSecondFactorType()
424
            ));
425
        }
426
    }
427
428
    /**
429
     * @param IdentityApi $identity
430
     * @return bool
431
     */
432
    private function emailVerificationIsRequired(IdentityApi $identity)
433
    {
434
        $institution = new ConfigurationInstitution(
435
            (string) $identity->getInstitution()
436
        );
437
438
        $configuration = $this->institutionConfigurationOptionsService
439
            ->findInstitutionConfigurationOptionsFor($institution);
440
441
        if ($configuration === null) {
442
            return true;
443
        }
444
445
        return $configuration->verifyEmailOption->isEnabled();
446
    }
447
}
448