Completed
Push — feature/ra-optional-vetting ( 03a5ff...241f76 )
by
unknown
02:24
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\AggregateNotFoundException;
23
use Broadway\Repository\Repository as 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\Helper\SecondFactorProvePossessionHelper;
29
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
30
use Surfnet\Stepup\Identity\Entity\ConfigurableSettings;
31
use Surfnet\Stepup\Identity\Identity;
32
use Surfnet\Stepup\Identity\Value\CommonName;
33
use Surfnet\Stepup\Identity\Value\DocumentNumber;
34
use Surfnet\Stepup\Identity\Value\Email;
35
use Surfnet\Stepup\Identity\Value\GssfId;
36
use Surfnet\Stepup\Identity\Value\IdentityId;
37
use Surfnet\Stepup\Identity\Value\Institution;
38
use Surfnet\Stepup\Identity\Value\Locale;
39
use Surfnet\Stepup\Identity\Value\NameId;
40
use Surfnet\Stepup\Identity\Value\PhoneNumber;
41
use Surfnet\Stepup\Identity\Value\SecondFactorId;
42
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifierFactory;
43
use Surfnet\Stepup\Identity\Value\StepupProvider;
44
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
45
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
46
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
47
use Surfnet\StepupBundle\Value\SecondFactorType;
48
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
49
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
50
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
51
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
56
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
57
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
58
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveU2fDevicePossessionCommand;
59
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveYubikeyPossessionCommand;
60
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
61
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
62
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
63
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
64
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
65
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler\Exception\DuplicateIdentityException;
66
67
/**
68
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
69
 * @SuppressWarnings(PHPMD.TooManyMethods)
70
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
71
 */
72
class IdentityCommandHandler extends SimpleCommandHandler
73
{
74
    /**
75
     * @var \Surfnet\Stepup\Identity\EventSourcing\IdentityRepository
76
     */
77
    private $eventSourcedRepository;
78
79
    /**
80
     * @var IdentityRepository
81
     */
82
    private $identityProjectionRepository;
83
84
    /**
85
     * @var \Surfnet\Stepup\Identity\Entity\ConfigurableSettings
86
     */
87
    private $configurableSettings;
88
89
    /**
90
     * @var AllowedSecondFactorListService
91
     */
92
    private $allowedSecondFactorListService;
93
94
    /** @var SecondFactorTypeService */
95
    private $secondFactorTypeService;
96
97
    /**
98
     * @var InstitutionConfigurationOptionsService
99
     */
100
    private $institutionConfigurationOptionsService;
101
102
    /**
103
     * @var InstitutionConfigurationRepository
104
     */
105
    private $institutionConfigurationRepository;
106
    /**
107
     * @var SecondFactorProvePossessionHelper
108
     */
109
    private $provePossessionHelper;
110
111
    /**
112
     * @param RepositoryInterface $eventSourcedRepository
113
     * @param IdentityRepository $identityProjectionRepository
114
     * @param ConfigurableSettings $configurableSettings
115
     * @param AllowedSecondFactorListService $allowedSecondFactorListService
116
     * @param SecondFactorTypeService $secondFactorTypeService
117
     * @param SecondFactorProvePossessionHelper $provePossessionHelper
118
     * @param InstitutionConfigurationOptionsService $institutionConfigurationOptionsService
119
     * @param InstitutionConfigurationRepository $institutionConfigurationRepository
120
     */
121
    public function __construct(
122
        RepositoryInterface $eventSourcedRepository,
123
        IdentityRepository $identityProjectionRepository,
124
        ConfigurableSettings $configurableSettings,
125
        AllowedSecondFactorListService $allowedSecondFactorListService,
126
        SecondFactorTypeService $secondFactorTypeService,
127
        SecondFactorProvePossessionHelper $provePossessionHelper,
128
        InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
129
        InstitutionConfigurationRepository $institutionConfigurationRepository
130
    ) {
131
        $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...
132
        $this->identityProjectionRepository = $identityProjectionRepository;
133
        $this->configurableSettings = $configurableSettings;
134
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
135
        $this->secondFactorTypeService = $secondFactorTypeService;
136
        $this->provePossessionHelper = $provePossessionHelper;
137
        $this->institutionConfigurationOptionsService = $institutionConfigurationOptionsService;
138
        $this->institutionConfigurationRepository = $institutionConfigurationRepository;
139
    }
140
141
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
142
    {
143
        $preferredLocale = new Locale($command->preferredLocale);
144
        $this->assertIsValidLocale($preferredLocale);
145
146
        $identity = Identity::create(
147
            new IdentityId($command->id),
148
            new Institution($command->institution),
149
            new NameId($command->nameId),
150
            new CommonName($command->commonName),
151
            new Email($command->email),
152
            $preferredLocale
153
        );
154
155
        $this->eventSourcedRepository->save($identity);
156
    }
157
158
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command)
159
    {
160
        /** @var IdentityApi $identity */
161
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->id));
162
163
        $identity->rename(new CommonName($command->commonName));
164
        $identity->changeEmail(new Email($command->email));
165
166
        $this->eventSourcedRepository->save($identity);
167
    }
168
169
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
170
        BootstrapIdentityWithYubikeySecondFactorCommand $command
171
    ) {
172
        $preferredLocale = new Locale($command->preferredLocale);
173
        $this->assertIsValidLocale($preferredLocale);
174
175
        $institution = new Institution($command->institution);
176
        $nameId = new NameId($command->nameId);
177
178
        if ($this->identityProjectionRepository->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
179
            throw DuplicateIdentityException::forBootstrappingWithYubikeySecondFactor($nameId, $institution);
180
        }
181
182
        $identity = Identity::create(
183
            new IdentityId($command->identityId),
184
            $institution,
185
            $nameId,
186
            new CommonName($command->commonName),
187
            new Email($command->email),
188
            $preferredLocale
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
            $this->provePossessionHelper,
346
            $command->provePossessionSkipped
347
        );
348
349
        $this->eventSourcedRepository->save($authority);
350
        $this->eventSourcedRepository->save($registrant);
351
    }
352
353
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
354
    {
355
        /** @var IdentityApi $identity */
356
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
357
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
358
359
        $this->eventSourcedRepository->save($identity);
360
    }
361
362
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
363
    {
364
        /** @var IdentityApi $identity */
365
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
366
        $identity->complyWithSecondFactorRevocation(
367
            new SecondFactorId($command->secondFactorId),
368
            new IdentityId($command->authorityId)
369
        );
370
371
        $this->eventSourcedRepository->save($identity);
372
    }
373
374
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
375
    {
376
        $preferredLocale = new Locale($command->preferredLocale);
377
        $this->assertIsValidLocale($preferredLocale);
378
379
        /** @var IdentityApi $identity */
380
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
381
        $identity->expressPreferredLocale($preferredLocale);
382
383
        $this->eventSourcedRepository->save($identity);
384
    }
385
386
    /**
387
     * @param Locale $locale
388
     */
389
    private function assertIsValidLocale(Locale $locale)
390
    {
391
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
392
            throw new UnsupportedLocaleException(
393
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
394
            );
395
        }
396
    }
397
398
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
399
    {
400
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
401
            new ConfigurationInstitution($institution->getInstitution())
402
        );
403
404
        if (!$allowedSecondFactorList->allows($secondFactor)) {
405
            throw new SecondFactorNotAllowedException(sprintf(
406
                'Institution "%s" does not support second factor "%s"',
407
                $institution->getInstitution(),
408
                $secondFactor->getSecondFactorType()
409
            ));
410
        }
411
    }
412
413
    /**
414
     * @param IdentityApi $identity
415
     * @return bool
416
     */
417
    private function emailVerificationIsRequired(IdentityApi $identity)
418
    {
419
        $institution = new ConfigurationInstitution(
420
            (string) $identity->getInstitution()
421
        );
422
423
        $configuration = $this->institutionConfigurationOptionsService
424
            ->findInstitutionConfigurationOptionsFor($institution);
425
426
        if ($configuration === null) {
427
            return true;
428
        }
429
430
        return $configuration->verifyEmailOption->isEnabled();
431
    }
432
}
433