Completed
Pull Request — develop (#108)
by Boy
11:33 queued 07:58
created

VettingService::vet()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
rs 8.8571
cc 2
eloc 17
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\StepupRa\RaBundle\Service;
20
21
use RuntimeException;
22
use Surfnet\StepupBundle\Command\SendSmsChallengeCommand;
23
use Surfnet\StepupBundle\Command\VerifyPossessionOfPhoneCommand;
24
use Surfnet\StepupBundle\Service\SmsSecondFactor\OtpVerification;
25
use Surfnet\StepupBundle\Service\SmsSecondFactorService;
26
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber;
27
use Surfnet\StepupBundle\Value\SecondFactorType;
28
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\VetSecondFactorCommand;
29
use Surfnet\StepupRa\RaBundle\Command\CreateU2fSignRequestCommand;
30
use Surfnet\StepupRa\RaBundle\Command\StartVettingProcedureCommand;
31
use Surfnet\StepupRa\RaBundle\Command\VerifyIdentityCommand;
32
use Surfnet\StepupRa\RaBundle\Command\VerifyU2fAuthenticationCommand;
33
use Surfnet\StepupRa\RaBundle\Command\VerifyYubikeyPublicIdCommand;
34
use Surfnet\StepupRa\RaBundle\Exception\DomainException;
35
use Surfnet\StepupRa\RaBundle\Exception\InvalidArgumentException;
36
use Surfnet\StepupRa\RaBundle\Exception\LoaTooLowException;
37
use Surfnet\StepupRa\RaBundle\Exception\UnknownVettingProcedureException;
38
use Surfnet\StepupRa\RaBundle\Repository\VettingProcedureRepository;
39
use Surfnet\StepupRa\RaBundle\Service\Gssf\VerificationResult as GssfVerificationResult;
40
use Surfnet\StepupRa\RaBundle\Service\U2f\AuthenticationVerificationResult;
41
use Surfnet\StepupRa\RaBundle\Service\U2f\SignRequestCreationResult;
42
use Surfnet\StepupRa\RaBundle\VettingProcedure;
43
use Surfnet\StepupU2fBundle\Dto\SignRequest;
44
use Surfnet\StepupU2fBundle\Dto\SignResponse;
45
use Symfony\Component\Translation\TranslatorInterface;
46
47
/**
48
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
49
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
50
 */
51
class VettingService
52
{
53
    /**
54
     * @var \Surfnet\StepupBundle\Service\SmsSecondFactorService
55
     */
56
    private $smsSecondFactorService;
57
58
    /**
59
     * @var \Surfnet\StepupRa\RaBundle\Service\YubikeySecondFactorService
60
     */
61
    private $yubikeySecondFactorService;
62
63
    /**
64
     * @var \Surfnet\StepupRa\RaBundle\Service\GssfService
65
     */
66
    private $gssfService;
67
68
    /**
69
     * @var \Surfnet\StepupRa\RaBundle\Service\U2fService
70
     */
71
    private $u2fService;
72
73
    /**
74
     * @var \Surfnet\StepupRa\RaBundle\Service\CommandService
75
     */
76
    private $commandService;
77
78
    /**
79
     * @var \Surfnet\StepupRa\RaBundle\Repository\VettingProcedureRepository
80
     */
81
    private $vettingProcedureRepository;
82
83
    /**
84
     * @var \Symfony\Component\Translation\TranslatorInterface
85
     */
86
    private $translator;
87
88
    /**
89
     * @var \Surfnet\StepupRa\RaBundle\Service\IdentityService
90
     */
91
    private $identityService;
92
93
    public function __construct(
94
        SmsSecondFactorService $smsSecondFactorService,
95
        YubikeySecondFactorService $yubikeySecondFactorService,
96
        GssfService $gssfService,
97
        U2fService $u2fService,
98
        CommandService $commandService,
99
        VettingProcedureRepository $vettingProcedureRepository,
100
        TranslatorInterface $translator,
101
        IdentityService $identityService
102
    ) {
103
        $this->smsSecondFactorService = $smsSecondFactorService;
104
        $this->yubikeySecondFactorService = $yubikeySecondFactorService;
105
        $this->gssfService = $gssfService;
106
        $this->u2fService = $u2fService;
107
        $this->commandService = $commandService;
108
        $this->vettingProcedureRepository = $vettingProcedureRepository;
109
        $this->translator = $translator;
110
        $this->identityService = $identityService;
111
    }
112
113
    /**
114
     * @param StartVettingProcedureCommand $command
115
     * @return bool
116
     */
117
    public function isLoaSufficientToStartProcedure(StartVettingProcedureCommand $command)
118
    {
119
        $secondFactorType = new SecondFactorType($command->secondFactor->type);
120
121
        return $secondFactorType->isSatisfiedBy($command->authorityLoa);
122
    }
123
124
    /**
125
     * @param StartVettingProcedureCommand $command
126
     * @return string The procedure ID.
127
     */
128
    public function startProcedure(StartVettingProcedureCommand $command)
129
    {
130
        $this->smsSecondFactorService->clearSmsVerificationState();
131
132
        if (!$this->isLoaSufficientToStartProcedure($command)) {
133
            throw new LoaTooLowException(
134
                sprintf(
135
                    "Registration authority has LoA '%s', which is not enough to allow vetting of a '%s' second factor",
136
                    $command->authorityLoa,
137
                    $command->secondFactor->type
138
                )
139
            );
140
        }
141
142
        $procedure = VettingProcedure::start(
143
            $command->secondFactor->id,
144
            $command->authorityId,
145
            $command->registrationCode,
146
            $command->secondFactor
147
        );
148
149
        $this->vettingProcedureRepository->store($procedure);
150
151
        return $procedure->getId();
152
    }
153
154
    /**
155
     * @param string $procedureId
156
     * @throws UnknownVettingProcedureException
157
     */
158 View Code Duplication
    public function cancelProcedure($procedureId)
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...
159
    {
160
        if (!is_string($procedureId)) {
161
            throw InvalidArgumentException::invalidType('string', 'procedureId', $procedureId);
162
        }
163
164
        $procedure = $this->vettingProcedureRepository->retrieve($procedureId);
165
166
        if (!$procedure) {
167
            throw new UnknownVettingProcedureException(
168
                sprintf("No vetting procedure with id '%s' is known.", $procedureId)
169
            );
170
        }
171
172
        $this->vettingProcedureRepository->remove($procedureId);
173
    }
174
175
    /**
176
     * @return int
177
     */
178
    public function getSmsOtpRequestsRemainingCount()
179
    {
180
        return $this->smsSecondFactorService->getOtpRequestsRemainingCount();
181
    }
182
183
    /**
184
     * @return int
185
     */
186
    public function getSmsMaximumOtpRequestsCount()
187
    {
188
        return $this->smsSecondFactorService->getMaximumOtpRequestsCount();
189
    }
190
191
    /**
192
     * @param string $procedureId
193
     * @param SendSmsChallengeCommand $command
194
     * @return bool
195
     * @throws UnknownVettingProcedureException
196
     * @throws RuntimeException
197
     */
198
    public function sendSmsChallenge($procedureId, SendSmsChallengeCommand $command)
199
    {
200
        $procedure = $this->getProcedure($procedureId);
201
202
        $phoneNumber = InternationalPhoneNumber::fromStringFormat(
203
            $procedure->getSecondFactor()->secondFactorIdentifier
204
        );
205
206
        $identity = $this->identityService->findById($procedure->getSecondFactor()->identityId);
207
208
        if (!$identity) {
209
            throw new RuntimeException("Second factor is coupled to an identity that doesn't exist");
210
        }
211
212
        $command->phoneNumber = $phoneNumber;
213
        $command->body        = $this->translator->trans('ra.vetting.sms.challenge_body', [], 'messages', $identity->preferredLocale);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
214
        $command->identity    = $procedure->getSecondFactor()->identityId;
215
        $command->institution = $procedure->getSecondFactor()->institution;
216
217
        return $this->smsSecondFactorService->sendChallenge($command);
218
    }
219
220
    /**
221
     * @param string                   $procedureId
222
     * @param VerifyPossessionOfPhoneCommand $command
223
     * @return OtpVerification
224
     * @throws UnknownVettingProcedureException
225
     * @throws DomainException
226
     */
227
    public function verifyPhoneNumber($procedureId, VerifyPossessionOfPhoneCommand $command)
228
    {
229
        $procedure = $this->getProcedure($procedureId);
230
231
        $verification = $this->smsSecondFactorService->verifyPossession($command);
232
233
        if (!$verification->wasSuccessful()) {
234
            return $verification;
235
        }
236
237
        $procedure->verifySecondFactorIdentifier($verification->getPhoneNumber());
238
        $this->vettingProcedureRepository->store($procedure);
239
240
        return $verification;
241
    }
242
243
    /**
244
     * @param string                       $procedureId
245
     * @param VerifyYubikeyPublicIdCommand $command
246
     * @return YubikeySecondFactor\VerificationResult
247
     */
248
    public function verifyYubikeyPublicId($procedureId, VerifyYubikeyPublicIdCommand $command)
249
    {
250
        $procedure = $this->getProcedure($procedureId);
251
252
        $command->expectedPublicId = $procedure->getSecondFactor()->secondFactorIdentifier;
253
        $command->identityId = $procedure->getSecondFactor()->identityId;
254
        $command->institution = $procedure->getSecondFactor()->institution;
255
256
        $result = $this->yubikeySecondFactorService->verifyYubikeyPublicId($command);
257
258
        if ($result->didPublicIdMatch()) {
259
            $procedure->verifySecondFactorIdentifier($result->getPublicId()->getYubikeyPublicId());
260
261
            $this->vettingProcedureRepository->store($procedure);
262
        }
263
264
        return $result;
265
    }
266
267
    /**
268
     * @param string $procedureId
269
     */
270
    public function startGssfVerification($procedureId)
271
    {
272
        $procedure = $this->getProcedure($procedureId);
273
274
        $this->gssfService->startVerification($procedure->getSecondFactor()->secondFactorIdentifier, $procedureId);
275
    }
276
277
    /**
278
     * @param string $gssfId
279
     * @return GssfVerificationResult
280
     */
281
    public function verifyGssfId($gssfId)
282
    {
283
        $result = $this->gssfService->verify($gssfId);
284
285
        if (!$result->isSuccess()) {
286
            return $result;
287
        }
288
289
        $procedure = $this->getProcedure($result->getProcedureId());
290
        $procedure->verifySecondFactorIdentifier($gssfId);
291
292
        $this->vettingProcedureRepository->store($procedure);
293
294
        return $result;
295
    }
296
297
    /**
298
     * @param string $procedureId
299
     * @return SignRequestCreationResult
300
     */
301
    public function createU2fSignRequest($procedureId)
302
    {
303
        $procedure = $this->getProcedure($procedureId);
304
305
        $command = new CreateU2fSignRequestCommand();
306
        $command->keyHandle = $procedure->getSecondFactor()->secondFactorIdentifier;
307
        $command->identityId = $procedure->getSecondFactor()->identityId;
308
        $command->institution = $procedure->getSecondFactor()->institution;
309
310
        return $this->u2fService->createSignRequest($command);
311
    }
312
313
    /**
314
     * @param string       $procedureId
315
     * @param SignRequest  $signRequest
316
     * @param SignResponse $signResponse
317
     * @return AuthenticationVerificationResult
318
     */
319
    public function verifyU2fAuthentication($procedureId, SignRequest $signRequest, SignResponse $signResponse)
320
    {
321
        $procedure = $this->getProcedure($procedureId);
322
323
        $command = new VerifyU2fAuthenticationCommand();
324
        $command->identityId = $procedure->getSecondFactor()->identityId;
325
        $command->institution = $procedure->getSecondFactor()->institution;
326
        $command->signRequest = $signRequest;
327
        $command->signResponse = $signResponse;
328
329
        $result = $this->u2fService->verifyAuthentication($command);
330
331
        if ($result->wasSuccessful()) {
332
            $procedure->verifySecondFactorIdentifier($signResponse->keyHandle);
333
            $this->vettingProcedureRepository->store($procedure);
334
        }
335
336
        return $result;
337
    }
338
339
    /**
340
     * @param string $procedureId
341
     * @param VerifyIdentityCommand $command
342
     * @return void
343
     * @throws UnknownVettingProcedureException
344
     * @throws DomainException
345
     */
346
    public function verifyIdentity($procedureId, VerifyIdentityCommand $command)
347
    {
348
        $procedure = $this->getProcedure($procedureId);
349
        $procedure->verifyIdentity($command->documentNumber, $command->identityVerified);
350
351
        $this->vettingProcedureRepository->store($procedure);
352
    }
353
354
    /**
355
     * @param string $procedureId
356
     * @return bool
357
     * @throws UnknownVettingProcedureException
358
     * @throws DomainException
359
     */
360
    public function vet($procedureId)
361
    {
362
        $procedure = $this->getProcedure($procedureId);
363
        $procedure->vet();
364
365
        $command = new VetSecondFactorCommand();
366
        $command->authorityId = $procedure->getAuthorityId();
367
        $command->identityId = $procedure->getSecondFactor()->identityId;
368
        $command->secondFactorId = $procedure->getSecondFactor()->id;
369
        $command->registrationCode = $procedure->getRegistrationCode();
370
        $command->secondFactorType = $procedure->getSecondFactor()->type;
371
        $command->secondFactorIdentifier = $procedure->getInputSecondFactorIdentifier();
372
        $command->documentNumber = $procedure->getDocumentNumber();
373
        $command->identityVerified = $procedure->isIdentityVerified();
374
375
        $result = $this->commandService->execute($command);
376
377
        if (!$result->isSuccessful()) {
378
            return false;
379
        }
380
381
        $this->vettingProcedureRepository->remove($procedureId);
382
383
        return true;
384
    }
385
386
    /**
387
     * @param string $procedureId
388
     * @return string
389
     * @throws UnknownVettingProcedureException
390
     */
391
    public function getIdentityCommonName($procedureId)
392
    {
393
        return $this->getProcedure($procedureId)->getSecondFactor()->commonName;
394
    }
395
396
    /**
397
     * @param $procedureId
398
     * @return string
399
     * @throws UnknownVettingProcedureException
400
     */
401
    public function getSecondFactorIdentifier($procedureId)
402
    {
403
        return $this->getProcedure($procedureId)->getSecondFactor()->secondFactorIdentifier;
404
    }
405
406
    /**
407
     * @param string $procedureId
408
     * @return null|VettingProcedure
409
     * @throws UnknownVettingProcedureException
410
     */
411 View Code Duplication
    private function getProcedure($procedureId)
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...
412
    {
413
        if (!is_string($procedureId)) {
414
            throw InvalidArgumentException::invalidType('string', 'procedureId', $procedureId);
415
        }
416
417
        $procedure = $this->vettingProcedureRepository->retrieve($procedureId);
418
419
        if (!$procedure) {
420
            throw new UnknownVettingProcedureException(
421
                sprintf("No vetting procedure with id '%s' is known.", $procedureId)
422
            );
423
        }
424
425
        return $procedure;
426
    }
427
428
    /**
429
     * @param string $procedureId
430
     * @return bool
431
     */
432
    public function hasProcedure($procedureId)
433
    {
434
        if (!is_string($procedureId)) {
435
            throw InvalidArgumentException::invalidType('string', 'procedureId', $procedureId);
436
        }
437
438
        return $this->vettingProcedureRepository->retrieve($procedureId) !== null;
439
    }
440
}
441