Completed
Push — feature/upgrade-remote-vetting ( bf22f6 )
by
unknown
02:39
created

handleRemoteVetSecondFactorCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
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\RemoteVetSecondFactorCommand;
58
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
59
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
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 handleRemoteVetSecondFactorCommand(RemoteVetSecondFactorCommand $command)
352
    {
353
        /** @var IdentityApi $identity */
354
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
355
356
        $identity->remoteVetSecondFactor(
0 ignored issues
show
Bug introduced by
The method remoteVetSecondFactor() 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...
357
            new SecondFactorId($command->secondFactorId)
358
        );
359
360
        $this->eventSourcedRepository->save($identity);
361
    }
362
363
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
364
    {
365
        /** @var IdentityApi $identity */
366
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
367
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
368
369
        $this->eventSourcedRepository->save($identity);
370
    }
371
372
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
373
    {
374
        /** @var IdentityApi $identity */
375
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
376
        $identity->complyWithSecondFactorRevocation(
377
            new SecondFactorId($command->secondFactorId),
378
            new IdentityId($command->authorityId)
379
        );
380
381
        $this->eventSourcedRepository->save($identity);
382
    }
383
384
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
385
    {
386
        $preferredLocale = new Locale($command->preferredLocale);
387
        $this->assertIsValidLocale($preferredLocale);
388
389
        /** @var IdentityApi $identity */
390
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
391
        $identity->expressPreferredLocale($preferredLocale);
392
393
        $this->eventSourcedRepository->save($identity);
394
    }
395
396
    /**
397
     * @param Locale $locale
398
     */
399
    private function assertIsValidLocale(Locale $locale)
400
    {
401
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
402
            throw new UnsupportedLocaleException(
403
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
404
            );
405
        }
406
    }
407
408
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
409
    {
410
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
411
            new ConfigurationInstitution($institution->getInstitution())
412
        );
413
414
        if (!$allowedSecondFactorList->allows($secondFactor)) {
415
            throw new SecondFactorNotAllowedException(sprintf(
416
                'Institution "%s" does not support second factor "%s"',
417
                $institution->getInstitution(),
418
                $secondFactor->getSecondFactorType()
419
            ));
420
        }
421
    }
422
423
    /**
424
     * @param IdentityApi $identity
425
     * @return bool
426
     */
427
    private function emailVerificationIsRequired(IdentityApi $identity)
428
    {
429
        $institution = new ConfigurationInstitution(
430
            (string) $identity->getInstitution()
431
        );
432
433
        $configuration = $this->institutionConfigurationOptionsService
434
            ->findInstitutionConfigurationOptionsFor($institution);
435
436
        if ($configuration === null) {
437
            return true;
438
        }
439
440
        return $configuration->verifyEmailOption->isEnabled();
441
    }
442
}
443