Completed
Push — master ( 64908a...328605 )
by
unknown
03:10
created

handleRevokeOwnSecondFactorCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 1
eloc 4
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\CommandHandler;
22
use Broadway\Repository\RepositoryInterface;
23
use Surfnet\Stepup\Configuration\Value\Institution as ConfigurationInstitution;
24
use Surfnet\Stepup\Identity\Api\Identity as IdentityApi;
25
use Surfnet\Stepup\Identity\Entity\ConfigurableSettings;
26
use Surfnet\Stepup\Identity\Identity;
27
use Surfnet\Stepup\Identity\Value\CommonName;
28
use Surfnet\Stepup\Identity\Value\DocumentNumber;
29
use Surfnet\Stepup\Identity\Value\Email;
30
use Surfnet\Stepup\Identity\Value\GssfId;
31
use Surfnet\Stepup\Identity\Value\IdentityId;
32
use Surfnet\Stepup\Identity\Value\Institution;
33
use Surfnet\Stepup\Identity\Value\Locale;
34
use Surfnet\Stepup\Identity\Value\NameId;
35
use Surfnet\Stepup\Identity\Value\PhoneNumber;
36
use Surfnet\Stepup\Identity\Value\SecondFactorId;
37
use Surfnet\Stepup\Identity\Value\SecondFactorIdentifierFactory;
38
use Surfnet\Stepup\Identity\Value\StepupProvider;
39
use Surfnet\Stepup\Identity\Value\U2fKeyHandle;
40
use Surfnet\Stepup\Identity\Value\YubikeyPublicId;
41
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
42
use Surfnet\StepupBundle\Value\SecondFactorType;
43
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
44
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
45
use Surfnet\StepupMiddleware\ApiBundle\Identity\Repository\IdentityRepository;
46
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
47
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
48
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
49
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
50
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
51
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveU2fDevicePossessionCommand;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveYubikeyPossessionCommand;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
56
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
57
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
58
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
59
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
60
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\CommandHandler\Exception\DuplicateIdentityException;
61
62
/**
63
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
64
 * @SuppressWarnings(PHPMD.TooManyMethods)
65
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
66
 */
67
class IdentityCommandHandler extends CommandHandler
68
{
69
    /**
70
     * @var \Surfnet\Stepup\Identity\EventSourcing\IdentityRepository
71
     */
72
    private $eventSourcedRepository;
73
74
    /**
75
     * @var IdentityRepository
76
     */
77
    private $identityProjectionRepository;
78
79
    /**
80
     * @var \Surfnet\Stepup\Identity\Entity\ConfigurableSettings
81
     */
82
    private $configurableSettings;
83
84
    /**
85
     * @var AllowedSecondFactorListService
86
     */
87
    private $allowedSecondFactorListService;
88
89
    /** @var SecondFactorTypeService */
90
    private $secondFactorTypeService;
91
92
    /**
93
     * @var InstitutionConfigurationOptionsService
94
     */
95
    private $institutionConfigurationOptionsService;
96
97
    /**
98
     * @var int
99
     */
100
    private $numberOfTokensPerIdentity;
101
102
    /**
103
     * @param RepositoryInterface                    $eventSourcedRepository
104
     * @param IdentityRepository                     $identityProjectionRepository
105
     * @param ConfigurableSettings                   $configurableSettings
106
     * @param AllowedSecondFactorListService         $allowedSecondFactorListService
107
     * @param SecondFactorTypeService                $secondFactorTypeService
108
     * @param InstitutionConfigurationOptionsService $institutionConfigurationOptionsService
109
     * @param int                                    $numberOfTokensPerIdentity
110
     */
111
    public function __construct(
112
        RepositoryInterface $eventSourcedRepository,
113
        IdentityRepository $identityProjectionRepository,
114
        ConfigurableSettings $configurableSettings,
115
        AllowedSecondFactorListService $allowedSecondFactorListService,
116
        SecondFactorTypeService $secondFactorTypeService,
117
        InstitutionConfigurationOptionsService $institutionConfigurationOptionsService,
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $institutionConfigurationOptionsService exceeds the maximum configured length of 30.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
118
        $numberOfTokensPerIdentity
119
    ) {
120
        $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...
121
        $this->identityProjectionRepository = $identityProjectionRepository;
122
        $this->configurableSettings = $configurableSettings;
123
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
124
        $this->secondFactorTypeService = $secondFactorTypeService;
125
        $this->institutionConfigurationOptionsService = $institutionConfigurationOptionsService;
126
        $this->numberOfTokensPerIdentity = $numberOfTokensPerIdentity;
127
    }
128
129
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
130
    {
131
        $preferredLocale = new Locale($command->preferredLocale);
132
        $this->assertIsValidLocale($preferredLocale);
133
134
        $identity = Identity::create(
135
            new IdentityId($command->id),
136
            new Institution($command->institution),
137
            new NameId($command->nameId),
138
            new CommonName($command->commonName),
139
            new Email($command->email),
140
            $preferredLocale
141
        );
142
143
        $this->eventSourcedRepository->save($identity);
144
    }
145
146
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command)
147
    {
148
        /** @var IdentityApi $identity */
149
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->id));
150
151
        $identity->rename(new CommonName($command->commonName));
152
        $identity->changeEmail(new Email($command->email));
153
154
        $this->eventSourcedRepository->save($identity);
155
    }
156
157
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
158
        BootstrapIdentityWithYubikeySecondFactorCommand $command
159
    ) {
160
        $preferredLocale = new Locale($command->preferredLocale);
161
        $this->assertIsValidLocale($preferredLocale);
162
163
        $institution = new Institution($command->institution);
164
        $nameId = new NameId($command->nameId);
165
166
        if ($this->identityProjectionRepository->hasIdentityWithNameIdAndInstitution($nameId, $institution)) {
167
            throw DuplicateIdentityException::forBootstrappingWithYubikeySecondFactor($nameId, $institution);
168
        }
169
170
        $identity = Identity::create(
171
            new IdentityId($command->identityId),
172
            $institution,
173
            $nameId,
174
            new CommonName($command->commonName),
175
            new Email($command->email),
176
            $preferredLocale
177
        );
178
179
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
180
181
        $identity->bootstrapYubikeySecondFactor(
182
            new SecondFactorId($command->secondFactorId),
183
            new YubikeyPublicId($command->yubikeyPublicId)
184
        );
185
186
        $this->eventSourcedRepository->save($identity);
187
    }
188
189 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...
190
    {
191
        /** @var IdentityApi $identity */
192
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
193
194
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
195
196
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('yubikey'), $identity->getInstitution());
197
198
        $identity->provePossessionOfYubikey(
199
            new SecondFactorId($command->secondFactorId),
200
            new YubikeyPublicId($command->yubikeyPublicId),
201
            $this->emailVerificationIsRequired($identity),
202
            $this->configurableSettings->createNewEmailVerificationWindow()
203
        );
204
205
        $this->eventSourcedRepository->save($identity);
206
    }
207
208
    /**
209
     * @param ProvePhonePossessionCommand $command
210
     */
211 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...
212
    {
213
        /** @var IdentityApi $identity */
214
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
215
216
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('sms'), $identity->getInstitution());
217
218
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
219
220
        $identity->provePossessionOfPhone(
221
            new SecondFactorId($command->secondFactorId),
222
            new PhoneNumber($command->phoneNumber),
223
            $this->emailVerificationIsRequired($identity),
224
            $this->configurableSettings->createNewEmailVerificationWindow()
225
        );
226
227
        $this->eventSourcedRepository->save($identity);
228
    }
229
230
    /**
231
     * @param ProveGssfPossessionCommand $command
232
     */
233
    public function handleProveGssfPossessionCommand(ProveGssfPossessionCommand $command)
234
    {
235
        /** @var IdentityApi $identity */
236
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
237
238
        $secondFactorType = $command->stepupProvider;
239
240
        // Validate that the chosen second factor type (stepupProvider) is allowed for the users instituti
241
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType($secondFactorType), $identity->getInstitution());
242
243
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
244
245
        $identity->provePossessionOfGssf(
246
            new SecondFactorId($command->secondFactorId),
247
            new StepupProvider($secondFactorType),
248
            new GssfId($command->gssfId),
249
            $this->emailVerificationIsRequired($identity),
250
            $this->configurableSettings->createNewEmailVerificationWindow()
251
        );
252
253
        $this->eventSourcedRepository->save($identity);
254
    }
255
256 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...
257
    {
258
        /** @var IdentityApi $identity */
259
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
260
261
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
262
263
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
264
265
        $identity->provePossessionOfU2fDevice(
266
            new SecondFactorId($command->secondFactorId),
267
            new U2fKeyHandle($command->keyHandle),
268
            $this->emailVerificationIsRequired($identity),
269
            $this->configurableSettings->createNewEmailVerificationWindow()
270
        );
271
272
        $this->eventSourcedRepository->save($identity);
273
    }
274
275
    /**
276
     * @param VerifyEmailCommand $command
277
     */
278
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
279
    {
280
        /** @var IdentityApi $identity */
281
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
282
283
        $identity->verifyEmail($command->verificationNonce);
284
285
        $this->eventSourcedRepository->save($identity);
286
    }
287
288
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
289
    {
290
        /** @var IdentityApi $authority */
291
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
292
        /** @var IdentityApi $registrant */
293
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
294
295
        $secondFactorType = new SecondFactorType($command->secondFactorType);
296
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
297
            $secondFactorType,
298
            $command->secondFactorIdentifier
299
        );
300
301
        $authority->vetSecondFactor(
302
            $registrant,
303
            new SecondFactorId($command->secondFactorId),
304
            $secondFactorType,
305
            $secondFactorIdentifier,
306
            $command->registrationCode,
307
            new DocumentNumber($command->documentNumber),
308
            $command->identityVerified,
309
            $this->secondFactorTypeService
310
        );
311
312
        $this->eventSourcedRepository->save($authority);
313
        $this->eventSourcedRepository->save($registrant);
314
    }
315
316
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
317
    {
318
        /** @var IdentityApi $identity */
319
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
320
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
321
322
        $this->eventSourcedRepository->save($identity);
323
    }
324
325
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
326
    {
327
        /** @var IdentityApi $identity */
328
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
329
        $identity->complyWithSecondFactorRevocation(
330
            new SecondFactorId($command->secondFactorId),
331
            new IdentityId($command->authorityId)
332
        );
333
334
        $this->eventSourcedRepository->save($identity);
335
    }
336
337
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
338
    {
339
        $preferredLocale = new Locale($command->preferredLocale);
340
        $this->assertIsValidLocale($preferredLocale);
341
342
        /** @var IdentityApi $identity */
343
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
344
        $identity->expressPreferredLocale($preferredLocale);
345
346
        $this->eventSourcedRepository->save($identity);
347
    }
348
349
    /**
350
     * @param Locale $locale
351
     */
352
    private function assertIsValidLocale(Locale $locale)
353
    {
354
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
355
            throw new UnsupportedLocaleException(
356
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
357
            );
358
        }
359
    }
360
361
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
362
    {
363
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
364
            new ConfigurationInstitution($institution->getInstitution())
365
        );
366
367
        if (!$allowedSecondFactorList->allows($secondFactor)) {
368
            throw new SecondFactorNotAllowedException(sprintf(
369
                'Institution "%s" does not support second factor "%s"',
370
                $institution->getInstitution(),
371
                $secondFactor->getSecondFactorType()
372
            ));
373
        }
374
    }
375
376
    /**
377
     * @param IdentityApi $identity
378
     * @return bool
379
     */
380
    private function emailVerificationIsRequired(IdentityApi $identity)
381
    {
382
        $institution = new ConfigurationInstitution(
383
            (string) $identity->getInstitution()
384
        );
385
386
        $configuration = $this->institutionConfigurationOptionsService
387
            ->findInstitutionConfigurationOptionsFor($institution);
388
389
        if ($configuration === null) {
390
            return true;
391
        }
392
393
        return $configuration->verifyEmailOption->isEnabled();
394
    }
395
}
396