Completed
Pull Request — feature/test-php-7-2-in-travis (#309)
by
unknown
04:53 queued 02:32
created

IdentityCommandHandler::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\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\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 SimpleCommandHandler
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
     * @var SecondFactorProvePossessionHelper
105
     */
106
    private $provePossessionHelper;
107
108
    /**
109
     * @param RepositoryInterface $eventSourcedRepository
110
     * @param IdentityRepository $identityProjectionRepository
111
     * @param ConfigurableSettings $configurableSettings
112
     * @param AllowedSecondFactorListService $allowedSecondFactorListService
113
     * @param SecondFactorTypeService $secondFactorTypeService
114
     * @param SecondFactorProvePossessionHelper $provePossessionHelper
115
     * @param InstitutionConfigurationOptionsService $institutionConfigurationOptionsService
116
     * @param InstitutionConfigurationRepository $institutionConfigurationRepository
117
     */
118
    public function __construct(
119
        RepositoryInterface $eventSourcedRepository,
120
        IdentityRepository $identityProjectionRepository,
121
        ConfigurableSettings $configurableSettings,
122
        AllowedSecondFactorListService $allowedSecondFactorListService,
123
        SecondFactorTypeService $secondFactorTypeService,
124
        SecondFactorProvePossessionHelper $provePossessionHelper,
125
        InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
126
        InstitutionConfigurationRepository $institutionConfigurationRepository
127
    ) {
128
        $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...
129
        $this->identityProjectionRepository = $identityProjectionRepository;
130
        $this->configurableSettings = $configurableSettings;
131
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
132
        $this->secondFactorTypeService = $secondFactorTypeService;
133
        $this->provePossessionHelper = $provePossessionHelper;
134
        $this->institutionConfigurationOptionsService = $institutionConfigurationOptionsService;
135
        $this->institutionConfigurationRepository = $institutionConfigurationRepository;
136
    }
137
138
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
139
    {
140
        $preferredLocale = new Locale($command->preferredLocale);
141
        $this->assertIsValidLocale($preferredLocale);
142
143
        $identity = Identity::create(
144
            new IdentityId($command->id),
145
            new Institution($command->institution),
146
            new NameId($command->nameId),
147
            new CommonName($command->commonName),
148
            new Email($command->email),
149
            $preferredLocale
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
        $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
        );
187
188
        $configurationInstitution = new ConfigurationInstitution(
189
            (string) $identity->getInstitution()
190
        );
191
192
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
193
        $identity->setMaxNumberOfTokens($tokenCount);
194
195
        $identity->bootstrapYubikeySecondFactor(
196
            new SecondFactorId($command->secondFactorId),
197
            new YubikeyPublicId($command->yubikeyPublicId)
198
        );
199
200
        $this->eventSourcedRepository->save($identity);
201
    }
202
203 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...
204
    {
205
        /** @var IdentityApi $identity */
206
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
207
208
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('yubikey'), $identity->getInstitution());
209
210
        $configurationInstitution = new ConfigurationInstitution(
211
            (string) $identity->getInstitution()
212
        );
213
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
214
        $identity->setMaxNumberOfTokens($tokenCount);
215
216
        $identity->provePossessionOfYubikey(
217
            new SecondFactorId($command->secondFactorId),
218
            new YubikeyPublicId($command->yubikeyPublicId),
219
            $this->emailVerificationIsRequired($identity),
220
            $this->configurableSettings->createNewEmailVerificationWindow()
221
        );
222
223
        $this->eventSourcedRepository->save($identity);
224
    }
225
226
    /**
227
     * @param ProvePhonePossessionCommand $command
228
     */
229 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...
230
    {
231
        /** @var IdentityApi $identity */
232
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
233
234
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('sms'), $identity->getInstitution());
235
236
        $configurationInstitution = new ConfigurationInstitution(
237
            (string) $identity->getInstitution()
238
        );
239
240
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
241
        $identity->setMaxNumberOfTokens($tokenCount);
242
243
        $identity->provePossessionOfPhone(
244
            new SecondFactorId($command->secondFactorId),
245
            new PhoneNumber($command->phoneNumber),
246
            $this->emailVerificationIsRequired($identity),
247
            $this->configurableSettings->createNewEmailVerificationWindow()
248
        );
249
250
        $this->eventSourcedRepository->save($identity);
251
    }
252
253
    /**
254
     * @param ProveGssfPossessionCommand $command
255
     */
256
    public function handleProveGssfPossessionCommand(ProveGssfPossessionCommand $command)
257
    {
258
        /** @var IdentityApi $identity */
259
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
260
        $secondFactorType = $command->stepupProvider;
261
262
        // Validate that the chosen second factor type (stepupProvider) is allowed for the users instituti
263
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType($secondFactorType), $identity->getInstitution());
264
265
        $configurationInstitution = new ConfigurationInstitution(
266
            (string) $identity->getInstitution()
267
        );
268
269
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
270
        $identity->setMaxNumberOfTokens($tokenCount);
271
272
        $identity->provePossessionOfGssf(
273
            new SecondFactorId($command->secondFactorId),
274
            new StepupProvider($secondFactorType),
275
            new GssfId($command->gssfId),
276
            $this->emailVerificationIsRequired($identity),
277
            $this->configurableSettings->createNewEmailVerificationWindow()
278
        );
279
280
        $this->eventSourcedRepository->save($identity);
281
    }
282
283 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...
284
    {
285
        /** @var IdentityApi $identity */
286
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
287
288
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
289
290
        $configurationInstitution = new ConfigurationInstitution(
291
            (string) $identity->getInstitution()
292
        );
293
294
        $tokenCount = $this->institutionConfigurationOptionsService->getMaxNumberOfTokensFor($configurationInstitution);
295
        $identity->setMaxNumberOfTokens($tokenCount);
296
297
        $identity->provePossessionOfU2fDevice(
298
            new SecondFactorId($command->secondFactorId),
299
            new U2fKeyHandle($command->keyHandle),
300
            $this->emailVerificationIsRequired($identity),
301
            $this->configurableSettings->createNewEmailVerificationWindow()
302
        );
303
304
        $this->eventSourcedRepository->save($identity);
305
    }
306
307
    /**
308
     * @param VerifyEmailCommand $command
309
     */
310
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
311
    {
312
        /** @var IdentityApi $identity */
313
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
314
315
        $identity->verifyEmail($command->verificationNonce);
316
317
        $this->eventSourcedRepository->save($identity);
318
    }
319
320
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
321
    {
322
        /** @var IdentityApi $authority */
323
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
324
        /** @var IdentityApi $registrant */
325
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
326
327
        $secondFactorType = new SecondFactorType($command->secondFactorType);
328
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
329
            $secondFactorType,
330
            $command->secondFactorIdentifier
331
        );
332
333
        $authority->vetSecondFactor(
334
            $registrant,
335
            new SecondFactorId($command->secondFactorId),
336
            $secondFactorType,
337
            $secondFactorIdentifier,
338
            $command->registrationCode,
339
            new DocumentNumber($command->documentNumber),
340
            $command->identityVerified,
341
            $this->secondFactorTypeService,
342
            $this->provePossessionHelper,
343
            $command->provePossessionSkipped
344
        );
345
346
        $this->eventSourcedRepository->save($authority);
347
        $this->eventSourcedRepository->save($registrant);
348
    }
349
350
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
351
    {
352
        /** @var IdentityApi $identity */
353
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
354
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
355
356
        $this->eventSourcedRepository->save($identity);
357
    }
358
359
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
360
    {
361
        /** @var IdentityApi $identity */
362
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
363
        $identity->complyWithSecondFactorRevocation(
364
            new SecondFactorId($command->secondFactorId),
365
            new IdentityId($command->authorityId)
366
        );
367
368
        $this->eventSourcedRepository->save($identity);
369
    }
370
371
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
372
    {
373
        $preferredLocale = new Locale($command->preferredLocale);
374
        $this->assertIsValidLocale($preferredLocale);
375
376
        /** @var IdentityApi $identity */
377
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
378
        $identity->expressPreferredLocale($preferredLocale);
379
380
        $this->eventSourcedRepository->save($identity);
381
    }
382
383
    /**
384
     * @param Locale $locale
385
     */
386
    private function assertIsValidLocale(Locale $locale)
387
    {
388
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
389
            throw new UnsupportedLocaleException(
390
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
391
            );
392
        }
393
    }
394
395
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
396
    {
397
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
398
            new ConfigurationInstitution($institution->getInstitution())
399
        );
400
401
        if (!$allowedSecondFactorList->allows($secondFactor)) {
402
            throw new SecondFactorNotAllowedException(sprintf(
403
                'Institution "%s" does not support second factor "%s"',
404
                $institution->getInstitution(),
405
                $secondFactor->getSecondFactorType()
406
            ));
407
        }
408
    }
409
410
    /**
411
     * @param IdentityApi $identity
412
     * @return bool
413
     */
414
    private function emailVerificationIsRequired(IdentityApi $identity)
415
    {
416
        $institution = new ConfigurationInstitution(
417
            (string) $identity->getInstitution()
418
        );
419
420
        $configuration = $this->institutionConfigurationOptionsService
421
            ->findInstitutionConfigurationOptionsFor($institution);
422
423
        if ($configuration === null) {
424
            return true;
425
        }
426
427
        return $configuration->verifyEmailOption->isEnabled();
428
    }
429
}
430