Completed
Push — develop ( 94f77d...523ff2 )
by A.
10s
created

handleProveU2fDevicePossessionCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

Duplication

Lines 15
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 15
loc 15
rs 9.4285
cc 1
eloc 8
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\Value\SecondFactorType;
42
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\AllowedSecondFactorListService;
43
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\SecondFactorNotAllowedException;
44
use Surfnet\StepupMiddleware\CommandHandlingBundle\Exception\UnsupportedLocaleException;
45
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\BootstrapIdentityWithYubikeySecondFactorCommand;
46
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\CreateIdentityCommand;
47
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ExpressLocalePreferenceCommand;
48
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveGssfPossessionCommand;
49
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProvePhonePossessionCommand;
50
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveU2fDevicePossessionCommand;
51
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\ProveYubikeyPossessionCommand;
52
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeOwnSecondFactorCommand;
53
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\RevokeRegistrantsSecondFactorCommand;
54
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\UpdateIdentityCommand;
55
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VerifyEmailCommand;
56
use Surfnet\StepupMiddleware\CommandHandlingBundle\Identity\Command\VetSecondFactorCommand;
57
58
/**
59
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
60
 * @SuppressWarnings(PHPMD.TooManyMethods)
61
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
62
 */
63
class IdentityCommandHandler extends CommandHandler
64
{
65
    /**
66
     * @var \Surfnet\Stepup\Identity\EventSourcing\IdentityRepository
67
     */
68
    private $repository;
69
70
    /**
71
     * @var \Surfnet\Stepup\Identity\Entity\ConfigurableSettings
72
     */
73
    private $configurableSettings;
74
75
    /**
76
     * @var AllowedSecondFactorListService
77
     */
78
    private $allowedSecondFactorListService;
79
80
    /**
81
     * @param RepositoryInterface $repository
82
     * @param ConfigurableSettings $configurableSettings
83
     * @param AllowedSecondFactorListService $allowedSecondFactorListService
84
     */
85
    public function __construct(
86
        RepositoryInterface $repository,
87
        ConfigurableSettings $configurableSettings,
88
        AllowedSecondFactorListService $allowedSecondFactorListService
89
    ) {
90
        $this->repository = $repository;
0 ignored issues
show
Documentation Bug introduced by
$repository is of type object<Broadway\Repository\RepositoryInterface>, but the property $repository 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...
91
        $this->configurableSettings = $configurableSettings;
92
        $this->allowedSecondFactorListService = $allowedSecondFactorListService;
93
    }
94
95
    public function handleCreateIdentityCommand(CreateIdentityCommand $command)
96
    {
97
        $preferredLocale = new Locale($command->preferredLocale);
98
        $this->assertIsValidLocale($preferredLocale);
99
100
        $identity = Identity::create(
101
            new IdentityId($command->id),
102
            new Institution($command->institution),
103
            new NameId($command->nameId),
104
            new CommonName($command->commonName),
105
            new Email($command->email),
106
            $preferredLocale
107
        );
108
109
        $this->repository->save($identity);
110
    }
111
112
    public function handleUpdateIdentityCommand(UpdateIdentityCommand $command)
113
    {
114
        /** @var IdentityApi $identity */
115
        $identity = $this->repository->load(new IdentityId($command->id));
116
117
        $identity->rename(new CommonName($command->commonName));
118
        $identity->changeEmail(new Email($command->email));
119
120
        $this->repository->save($identity);
121
    }
122
123
    public function handleBootstrapIdentityWithYubikeySecondFactorCommand(
124
        BootstrapIdentityWithYubikeySecondFactorCommand $command
125
    ) {
126
        $preferredLocale = new Locale($command->preferredLocale);
127
        $this->assertIsValidLocale($preferredLocale);
128
129
        // @todo add check if Identity does not already exist based on NameId
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
130
        $identity = Identity::create(
131
            new IdentityId($command->identityId),
132
            new Institution($command->institution),
133
            new NameId($command->nameId),
134
            new CommonName($command->commonName),
135
            new Email($command->email),
136
            $preferredLocale
137
        );
138
139
        $identity->bootstrapYubikeySecondFactor(
140
            new SecondFactorId($command->secondFactorId),
141
            new YubikeyPublicId($command->yubikeyPublicId)
142
        );
143
144
        $this->repository->save($identity);
145
    }
146
147 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...
148
    {
149
        /** @var IdentityApi $identity */
150
        $identity = $this->repository->load(new IdentityId($command->identityId));
151
152
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('yubikey'), $identity->getInstitution());
153
154
        $identity->provePossessionOfYubikey(
155
            new SecondFactorId($command->secondFactorId),
156
            new YubikeyPublicId($command->yubikeyPublicId),
157
            $this->configurableSettings->createNewEmailVerificationWindow()
158
        );
159
160
        $this->repository->save($identity);
161
    }
162
163
    /**
164
     * @param ProvePhonePossessionCommand $command
165
     */
166 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...
167
    {
168
        /** @var IdentityApi $identity */
169
        $identity = $this->repository->load(new IdentityId($command->identityId));
170
171
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('sms'), $identity->getInstitution());
172
173
        $identity->provePossessionOfPhone(
174
            new SecondFactorId($command->secondFactorId),
175
            new PhoneNumber($command->phoneNumber),
176
            $this->configurableSettings->createNewEmailVerificationWindow()
177
        );
178
179
        $this->repository->save($identity);
180
    }
181
182
    /**
183
     * @param ProveGssfPossessionCommand $command
184
     */
185 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...
186
    {
187
        /** @var IdentityApi $identity */
188
        $identity = $this->repository->load(new IdentityId($command->identityId));
189
190
        // Assume tiqr is being used as it is the only GSSF currently supported
191
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('tiqr'), $identity->getInstitution());
192
193
        $identity->provePossessionOfGssf(
194
            new SecondFactorId($command->secondFactorId),
195
            new StepupProvider($command->stepupProvider),
196
            new GssfId($command->gssfId),
197
            $this->configurableSettings->createNewEmailVerificationWindow()
198
        );
199
200
        $this->repository->save($identity);
201
    }
202
203 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...
204
    {
205
        /** @var IdentityApi $identity */
206
        $identity = $this->repository->load(new IdentityId($command->identityId));
207
208
        $this->assertSecondFactorIsAllowedFor(new SecondFactorType('u2f'), $identity->getInstitution());
209
210
        $identity->provePossessionOfU2fDevice(
211
            new SecondFactorId($command->secondFactorId),
212
            new U2fKeyHandle($command->keyHandle),
213
            $this->configurableSettings->createNewEmailVerificationWindow()
214
        );
215
216
        $this->repository->save($identity);
217
    }
218
219
    /**
220
     * @param VerifyEmailCommand $command
221
     */
222
    public function handleVerifyEmailCommand(VerifyEmailCommand $command)
223
    {
224
        /** @var IdentityApi $identity */
225
        $identity = $this->repository->load(new IdentityId($command->identityId));
226
227
        $identity->verifyEmail($command->verificationNonce);
228
229
        $this->repository->save($identity);
230
    }
231
232
    public function handleVetSecondFactorCommand(VetSecondFactorCommand $command)
233
    {
234
        /** @var IdentityApi $authority */
235
        $authority = $this->repository->load(new IdentityId($command->authorityId));
236
        /** @var IdentityApi $registrant */
237
        $registrant = $this->repository->load(new IdentityId($command->identityId));
238
239
        $secondFactorType = new SecondFactorType($command->secondFactorType);
240
        $secondFactorIdentifier = SecondFactorIdentifierFactory::forType(
241
            $secondFactorType,
242
            $command->secondFactorIdentifier
243
        );
244
245
        $authority->vetSecondFactor(
246
            $registrant,
247
            new SecondFactorId($command->secondFactorId),
248
            $secondFactorType,
249
            $secondFactorIdentifier,
250
            $command->registrationCode,
251
            new DocumentNumber($command->documentNumber),
252
            $command->identityVerified
253
        );
254
255
        $this->repository->save($authority);
256
        $this->repository->save($registrant);
257
    }
258
259
    public function handleRevokeOwnSecondFactorCommand(RevokeOwnSecondFactorCommand $command)
260
    {
261
        /** @var IdentityApi $identity */
262
        $identity = $this->repository->load(new IdentityId($command->identityId));
263
        $identity->revokeSecondFactor(new SecondFactorId($command->secondFactorId));
264
265
        $this->repository->save($identity);
266
    }
267
268
    public function handleRevokeRegistrantsSecondFactorCommand(RevokeRegistrantsSecondFactorCommand $command)
269
    {
270
        /** @var IdentityApi $identity */
271
        $identity = $this->repository->load(new IdentityId($command->identityId));
272
        $identity->complyWithSecondFactorRevocation(
273
            new SecondFactorId($command->secondFactorId),
274
            new IdentityId($command->authorityId)
275
        );
276
277
        $this->repository->save($identity);
278
    }
279
280
    public function handleExpressLocalePreferenceCommand(ExpressLocalePreferenceCommand $command)
281
    {
282
        $preferredLocale = new Locale($command->preferredLocale);
283
        $this->assertIsValidLocale($preferredLocale);
284
285
        /** @var IdentityApi $identity */
286
        $identity = $this->repository->load(new IdentityId($command->identityId));
287
        $identity->expressPreferredLocale($preferredLocale);
288
289
        $this->repository->save($identity);
290
    }
291
292
    /**
293
     * @param Locale $locale
294
     */
295
    private function assertIsValidLocale(Locale $locale)
296
    {
297
        if (!$this->configurableSettings->isSupportedLocale($locale)) {
298
            throw new UnsupportedLocaleException(
299
                sprintf('Given locale "%s" is not a supported locale', (string) $locale)
300
            );
301
        }
302
    }
303
304
    private function assertSecondFactorIsAllowedFor(SecondFactorType $secondFactor, Institution $institution)
305
    {
306
        $allowedSecondFactorList = $this->allowedSecondFactorListService->getAllowedSecondFactorListFor(
307
            new ConfigurationInstitution($institution->getInstitution())
308
        );
309
310
        if (!$allowedSecondFactorList->allows($secondFactor)) {
311
            throw new SecondFactorNotAllowedException(sprintf(
312
                'Institution "%s" does not support second factor "%s"',
313
                $institution->getInstitution(),
314
                $secondFactor->getSecondFactorType()
315
            ));
316
        }
317
    }
318
}
319