Completed
Push — develop ( 74f9b5...b7d438 )
by
unknown
11s
created

emailVerificationIsRequired()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
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 View Code Duplication
    public function handleProveGssfPossessionCommand(ProveGssfPossessionCommand $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...
234
    {
235
        /** @var IdentityApi $identity */
236
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
237
238
        // Assume tiqr is being used as it is the only GSSF currently supported
239
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('tiqr'), $identity->getInstitution());
240
241
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
242
243
        $identity->provePossessionOfGssf(
244
            new SecondFactorId($command->secondFactorId),
245
            new StepupProvider($command->stepupProvider),
246
            new GssfId($command->gssfId),
247
            $this->emailVerificationIsRequired($identity),
248
            $this->configurableSettings->createNewEmailVerificationWindow()
249
        );
250
251
        $this->eventSourcedRepository->save($identity);
252
    }
253
254 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...
255
    {
256
        /** @var IdentityApi $identity */
257
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
258
259
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
260
261
        $identity->setMaxNumberOfTokens($this->numberOfTokensPerIdentity);
262
263
        $identity->provePossessionOfU2fDevice(
264
            new SecondFactorId($command->secondFactorId),
265
            new U2fKeyHandle($command->keyHandle),
266
            $this->emailVerificationIsRequired($identity),
267
            $this->configurableSettings->createNewEmailVerificationWindow()
268
        );
269
270
        $this->eventSourcedRepository->save($identity);
271
    }
272
273
    /**
274
     * @param VerifyEmailCommand $command
275
     */
276
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
277
    {
278
        /** @var IdentityApi $identity */
279
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
280
281
        $identity->verifyEmail($command->verificationNonce);
282
283
        $this->eventSourcedRepository->save($identity);
284
    }
285
286
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
287
    {
288
        /** @var IdentityApi $authority */
289
        $authority = $this->eventSourcedRepository->load(new IdentityId($command->authorityId));
290
        /** @var IdentityApi $registrant */
291
        $registrant = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
292
293
        $secondFactorType = new SecondFactorType($command->secondFactorType);
294
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
295
            $secondFactorType,
296
            $command->secondFactorIdentifier
297
        );
298
299
        $authority->vetSecondFactor(
300
            $registrant,
301
            new SecondFactorId($command->secondFactorId),
302
            $secondFactorType,
303
            $secondFactorIdentifier,
304
            $command->registrationCode,
305
            new DocumentNumber($command->documentNumber),
306
            $command->identityVerified,
307
            $this->secondFactorTypeService
308
        );
309
310
        $this->eventSourcedRepository->save($authority);
311
        $this->eventSourcedRepository->save($registrant);
312
    }
313
314
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
315
    {
316
        /** @var IdentityApi $identity */
317
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
318
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
319
320
        $this->eventSourcedRepository->save($identity);
321
    }
322
323
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
324
    {
325
        /** @var IdentityApi $identity */
326
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
327
        $identity->complyWithSecondFactorRevocation(
328
            new SecondFactorId($command->secondFactorId),
329
            new IdentityId($command->authorityId)
330
        );
331
332
        $this->eventSourcedRepository->save($identity);
333
    }
334
335
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
336
    {
337
        $preferredLocale = new Locale($command->preferredLocale);
338
        $this->assertIsValidLocale($preferredLocale);
339
340
        /** @var IdentityApi $identity */
341
        $identity = $this->eventSourcedRepository->load(new IdentityId($command->identityId));
342
        $identity->expressPreferredLocale($preferredLocale);
343
344
        $this->eventSourcedRepository->save($identity);
345
    }
346
347
    /**
348
     * @param Locale $locale
349
     */
350
    private function assertIsValidLocale(Locale $locale)
351
    {
352
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
353
            throw new UnsupportedLocaleException(
354
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
355
            );
356
        }
357
    }
358
359
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
360
    {
361
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
362
            new ConfigurationInstitution($institution->getInstitution())
363
        );
364
365
        if (!$allowedSecondFactorList->allows($secondFactor)) {
366
            throw new SecondFactorNotAllowedException(sprintf(
367
                'Institution "%s" does not support second factor "%s"',
368
                $institution->getInstitution(),
369
                $secondFactor->getSecondFactorType()
370
            ));
371
        }
372
    }
373
374
    /**
375
     * @param IdentityApi $identity
376
     * @return bool
377
     */
378
    private function emailVerificationIsRequired(IdentityApi $identity)
379
    {
380
        $institution = new ConfigurationInstitution(
381
            (string) $identity->getInstitution()
382
        );
383
384
        $configuration = $this->institutionConfigurationOptionsService
385
            ->findInstitutionConfigurationOptionsFor($institution);
386
387
        if ($configuration === null) {
388
            return true;
389
        }
390
391
        return $configuration->verifyEmailOption->isEnabled();
392
    }
393
}
394