Completed
Push — feature/registration-expiratio... ( 0a4fba )
by
unknown
10:12
created

VettingService   D

Complexity

Total Complexity 32

Size/Duplication

Total Lines 412
Duplicated Lines 7.77 %

Coupling/Cohesion

Components 1
Dependencies 31

Importance

Changes 10
Bugs 0 Features 0
Metric Value
wmc 32
c 10
b 0
f 0
lcom 1
cbo 31
dl 32
loc 412
rs 4.8

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 1
A isLoaSufficientToStartProcedure() 0 6 1
A isExpiredRegistrationCode() 0 9 1
B startProcedure() 0 25 2
A cancelProcedure() 16 16 3
A getSmsOtpRequestsRemainingCount() 0 4 1
A getSmsMaximumOtpRequestsCount() 0 4 1
A sendSmsChallenge() 0 21 2
A verifyPhoneNumber() 0 15 2
A verifyYubikeyPublicId() 0 18 2
A startGssfVerification() 0 6 1
A verifyGssfId() 0 15 2
A createU2fSignRequest() 0 11 1
A verifyU2fAuthentication() 0 19 2
A verifyIdentity() 0 7 1
A vet() 0 23 2
A getIdentityCommonName() 0 4 1
A getSecondFactorIdentifier() 0 4 1
A getProcedure() 16 16 3
A hasProcedure() 0 8 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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\SecondFactorTypeService;
25
use Surfnet\StepupBundle\Service\SmsSecondFactor\OtpVerification;
26
use Surfnet\StepupBundle\Service\SmsSecondFactorService;
27
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber;
28
use Surfnet\StepupBundle\Value\SecondFactorType;
29
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\VetSecondFactorCommand;
30
use Surfnet\StepupRa\RaBundle\Command\CreateU2fSignRequestCommand;
31
use Surfnet\StepupRa\RaBundle\Command\StartVettingProcedureCommand;
32
use Surfnet\StepupRa\RaBundle\Command\VerifyIdentityCommand;
33
use Surfnet\StepupRa\RaBundle\Command\VerifyU2fAuthenticationCommand;
34
use Surfnet\StepupRa\RaBundle\Command\VerifyYubikeyPublicIdCommand;
35
use Surfnet\StepupRa\RaBundle\Exception\DomainException;
36
use Surfnet\StepupRa\RaBundle\Exception\InvalidArgumentException;
37
use Surfnet\StepupRa\RaBundle\Exception\LoaTooLowException;
38
use Surfnet\StepupRa\RaBundle\Exception\RegistrationCodeExpiredException;
39
use Surfnet\StepupRa\RaBundle\Exception\UnknownVettingProcedureException;
40
use Surfnet\StepupRa\RaBundle\Repository\VettingProcedureRepository;
41
use Surfnet\StepupRa\RaBundle\Service\Gssf\VerificationResult as GssfVerificationResult;
42
use Surfnet\StepupRa\RaBundle\Service\U2f\AuthenticationVerificationResult;
43
use Surfnet\StepupRa\RaBundle\Service\U2f\SignRequestCreationResult;
44
use Surfnet\StepupRa\RaBundle\Value\DateTime;
45
use Surfnet\StepupRa\RaBundle\VettingProcedure;
46
use Surfnet\StepupU2fBundle\Dto\SignRequest;
47
use Surfnet\StepupU2fBundle\Dto\SignResponse;
48
use Symfony\Component\Translation\TranslatorInterface;
49
50
/**
51
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
52
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
53
 */
54
class VettingService
55
{
56
    const REGISTRATION_CODE_EXPIRED_ERROR =
57
        'Surfnet\Stepup\Exception\DomainException: Cannot vet second factor, the registration window is closed.';
58
59
    /**
60
     * @var \Surfnet\StepupBundle\Service\SmsSecondFactorService
61
     */
62
    private $smsSecondFactorService;
63
64
    /**
65
     * @var \Surfnet\StepupRa\RaBundle\Service\YubikeySecondFactorService
66
     */
67
    private $yubikeySecondFactorService;
68
69
    /**
70
     * @var \Surfnet\StepupRa\RaBundle\Service\GssfService
71
     */
72
    private $gssfService;
73
74
    /**
75
     * @var \Surfnet\StepupRa\RaBundle\Service\U2fService
76
     */
77
    private $u2fService;
78
79
    /**
80
     * @var \Surfnet\StepupRa\RaBundle\Service\CommandService
81
     */
82
    private $commandService;
83
84
    /**
85
     * @var \Surfnet\StepupRa\RaBundle\Repository\VettingProcedureRepository
86
     */
87
    private $vettingProcedureRepository;
88
89
    /**
90
     * @var \Symfony\Component\Translation\TranslatorInterface
91
     */
92
    private $translator;
93
94
    /**
95
     * @var \Surfnet\StepupRa\RaBundle\Service\IdentityService
96
     */
97
    private $identityService;
98
99
    /**
100
     * @var \Surfnet\StepupBundle\Service\SecondFactorTypeService
101
     */
102
    private $secondFactorTypeService;
103
104
    public function __construct(
105
        SmsSecondFactorService $smsSecondFactorService,
106
        YubikeySecondFactorService $yubikeySecondFactorService,
107
        GssfService $gssfService,
108
        U2fService $u2fService,
109
        CommandService $commandService,
110
        VettingProcedureRepository $vettingProcedureRepository,
111
        TranslatorInterface $translator,
112
        IdentityService $identityService,
113
        SecondFactorTypeService $secondFactorTypeService
114
    ) {
115
        $this->smsSecondFactorService = $smsSecondFactorService;
116
        $this->yubikeySecondFactorService = $yubikeySecondFactorService;
117
        $this->gssfService = $gssfService;
118
        $this->u2fService = $u2fService;
119
        $this->commandService = $commandService;
120
        $this->vettingProcedureRepository = $vettingProcedureRepository;
121
        $this->translator = $translator;
122
        $this->identityService = $identityService;
123
        $this->secondFactorTypeService = $secondFactorTypeService;
124
    }
125
126
    /**
127
     * @param StartVettingProcedureCommand $command
128
     * @return bool
129
     */
130
    public function isLoaSufficientToStartProcedure(StartVettingProcedureCommand $command)
131
    {
132
        $secondFactorType = new SecondFactorType($command->secondFactor->type);
133
134
        return $this->secondFactorTypeService->isSatisfiedBy($secondFactorType, $command->authorityLoa);
135
    }
136
137
    /**
138
     * @param StartVettingProcedureCommand $command
139
     * @return bool
140
     */
141
    public function isExpiredRegistrationCode(StartVettingProcedureCommand $command)
142
    {
143
        return DateTime::now()->comesAfter(
144
            new DateTime(
145
                $command->secondFactor->registrationRequestedAt
0 ignored issues
show
Bug introduced by
The property registrationRequestedAt does not seem to exist in Surfnet\StepupMiddleware...to\VerifiedSecondFactor.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
146
                    ->add(new \DateInterval('P14D'))
147
            )
148
        );
149
    }
150
151
    /**
152
     * @param StartVettingProcedureCommand $command
153
     * @return string The procedure ID.
154
     */
155
    public function startProcedure(StartVettingProcedureCommand $command)
156
    {
157
        $this->smsSecondFactorService->clearSmsVerificationState();
158
159
        if (!$this->isLoaSufficientToStartProcedure($command)) {
160
            throw new LoaTooLowException(
161
                sprintf(
162
                    "Registration authority has LoA '%s', which is not enough to allow vetting of a '%s' second factor",
163
                    $command->authorityLoa,
164
                    $command->secondFactor->type
165
                )
166
            );
167
        }
168
169
        $procedure = VettingProcedure::start(
170
            $command->secondFactor->id,
171
            $command->authorityId,
172
            $command->registrationCode,
173
            $command->secondFactor
174
        );
175
176
        $this->vettingProcedureRepository->store($procedure);
177
178
        return $procedure->getId();
179
    }
180
181
    /**
182
     * @param string $procedureId
183
     * @throws UnknownVettingProcedureException
184
     */
185 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...
186
    {
187
        if (!is_string($procedureId)) {
188
            throw InvalidArgumentException::invalidType('string', 'procedureId', $procedureId);
189
        }
190
191
        $procedure = $this->vettingProcedureRepository->retrieve($procedureId);
192
193
        if (!$procedure) {
194
            throw new UnknownVettingProcedureException(
195
                sprintf("No vetting procedure with id '%s' is known.", $procedureId)
196
            );
197
        }
198
199
        $this->vettingProcedureRepository->remove($procedureId);
200
    }
201
202
    /**
203
     * @return int
204
     */
205
    public function getSmsOtpRequestsRemainingCount()
206
    {
207
        return $this->smsSecondFactorService->getOtpRequestsRemainingCount();
208
    }
209
210
    /**
211
     * @return int
212
     */
213
    public function getSmsMaximumOtpRequestsCount()
214
    {
215
        return $this->smsSecondFactorService->getMaximumOtpRequestsCount();
216
    }
217
218
    /**
219
     * @param string $procedureId
220
     * @param SendSmsChallengeCommand $command
221
     * @return bool
222
     * @throws UnknownVettingProcedureException
223
     * @throws RuntimeException
224
     */
225
    public function sendSmsChallenge($procedureId, SendSmsChallengeCommand $command)
226
    {
227
        $procedure = $this->getProcedure($procedureId);
228
229
        $phoneNumber = InternationalPhoneNumber::fromStringFormat(
230
            $procedure->getSecondFactor()->secondFactorIdentifier
231
        );
232
233
        $identity = $this->identityService->findById($procedure->getSecondFactor()->identityId);
234
235
        if (!$identity) {
236
            throw new RuntimeException("Second factor is coupled to an identity that doesn't exist");
237
        }
238
239
        $command->phoneNumber = $phoneNumber;
240
        $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...
241
        $command->identity    = $procedure->getSecondFactor()->identityId;
242
        $command->institution = $procedure->getSecondFactor()->institution;
243
244
        return $this->smsSecondFactorService->sendChallenge($command);
245
    }
246
247
    /**
248
     * @param string                   $procedureId
249
     * @param VerifyPossessionOfPhoneCommand $command
250
     * @return OtpVerification
251
     * @throws UnknownVettingProcedureException
252
     * @throws DomainException
253
     */
254
    public function verifyPhoneNumber($procedureId, VerifyPossessionOfPhoneCommand $command)
255
    {
256
        $procedure = $this->getProcedure($procedureId);
257
258
        $verification = $this->smsSecondFactorService->verifyPossession($command);
259
260
        if (!$verification->wasSuccessful()) {
261
            return $verification;
262
        }
263
264
        $procedure->verifySecondFactorIdentifier($verification->getPhoneNumber());
265
        $this->vettingProcedureRepository->store($procedure);
0 ignored issues
show
Bug introduced by
It seems like $procedure defined by $this->getProcedure($procedureId) on line 256 can be null; however, Surfnet\StepupRa\RaBundl...dureRepository::store() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
266
267
        return $verification;
268
    }
269
270
    /**
271
     * @param string                       $procedureId
272
     * @param VerifyYubikeyPublicIdCommand $command
273
     * @return YubikeySecondFactor\VerificationResult
274
     */
275
    public function verifyYubikeyPublicId($procedureId, VerifyYubikeyPublicIdCommand $command)
276
    {
277
        $procedure = $this->getProcedure($procedureId);
278
279
        $command->expectedPublicId = $procedure->getSecondFactor()->secondFactorIdentifier;
280
        $command->identityId = $procedure->getSecondFactor()->identityId;
281
        $command->institution = $procedure->getSecondFactor()->institution;
282
283
        $result = $this->yubikeySecondFactorService->verifyYubikeyPublicId($command);
284
285
        if ($result->didPublicIdMatch()) {
286
            $procedure->verifySecondFactorIdentifier($result->getPublicId()->getYubikeyPublicId());
287
288
            $this->vettingProcedureRepository->store($procedure);
0 ignored issues
show
Bug introduced by
It seems like $procedure defined by $this->getProcedure($procedureId) on line 277 can be null; however, Surfnet\StepupRa\RaBundl...dureRepository::store() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
289
        }
290
291
        return $result;
292
    }
293
294
    /**
295
     * @param string $procedureId
296
     */
297
    public function startGssfVerification($procedureId)
298
    {
299
        $procedure = $this->getProcedure($procedureId);
300
301
        $this->gssfService->startVerification($procedure->getSecondFactor()->secondFactorIdentifier, $procedureId);
302
    }
303
304
    /**
305
     * @param string $gssfId
306
     * @return GssfVerificationResult
307
     */
308
    public function verifyGssfId($gssfId)
309
    {
310
        $result = $this->gssfService->verify($gssfId);
311
312
        if (!$result->isSuccess()) {
313
            return $result;
314
        }
315
316
        $procedure = $this->getProcedure($result->getProcedureId());
317
        $procedure->verifySecondFactorIdentifier($gssfId);
318
319
        $this->vettingProcedureRepository->store($procedure);
0 ignored issues
show
Bug introduced by
It seems like $procedure defined by $this->getProcedure($result->getProcedureId()) on line 316 can be null; however, Surfnet\StepupRa\RaBundl...dureRepository::store() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
320
321
        return $result;
322
    }
323
324
    /**
325
     * @param string $procedureId
326
     * @return SignRequestCreationResult
327
     */
328
    public function createU2fSignRequest($procedureId)
329
    {
330
        $procedure = $this->getProcedure($procedureId);
331
332
        $command = new CreateU2fSignRequestCommand();
333
        $command->keyHandle = $procedure->getSecondFactor()->secondFactorIdentifier;
334
        $command->identityId = $procedure->getSecondFactor()->identityId;
335
        $command->institution = $procedure->getSecondFactor()->institution;
336
337
        return $this->u2fService->createSignRequest($command);
338
    }
339
340
    /**
341
     * @param string       $procedureId
342
     * @param SignRequest  $signRequest
343
     * @param SignResponse $signResponse
344
     * @return AuthenticationVerificationResult
345
     */
346
    public function verifyU2fAuthentication($procedureId, SignRequest $signRequest, SignResponse $signResponse)
347
    {
348
        $procedure = $this->getProcedure($procedureId);
349
350
        $command = new VerifyU2fAuthenticationCommand();
351
        $command->identityId = $procedure->getSecondFactor()->identityId;
352
        $command->institution = $procedure->getSecondFactor()->institution;
353
        $command->signRequest = $signRequest;
354
        $command->signResponse = $signResponse;
355
356
        $result = $this->u2fService->verifyAuthentication($command);
357
358
        if ($result->wasSuccessful()) {
359
            $procedure->verifySecondFactorIdentifier($signResponse->keyHandle);
360
            $this->vettingProcedureRepository->store($procedure);
0 ignored issues
show
Bug introduced by
It seems like $procedure defined by $this->getProcedure($procedureId) on line 348 can be null; however, Surfnet\StepupRa\RaBundl...dureRepository::store() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
361
        }
362
363
        return $result;
364
    }
365
366
    /**
367
     * @param string $procedureId
368
     * @param VerifyIdentityCommand $command
369
     * @return void
370
     * @throws UnknownVettingProcedureException
371
     * @throws DomainException
372
     */
373
    public function verifyIdentity($procedureId, VerifyIdentityCommand $command)
374
    {
375
        $procedure = $this->getProcedure($procedureId);
376
        $procedure->verifyIdentity($command->documentNumber, $command->identityVerified);
377
378
        $this->vettingProcedureRepository->store($procedure);
0 ignored issues
show
Bug introduced by
It seems like $procedure defined by $this->getProcedure($procedureId) on line 375 can be null; however, Surfnet\StepupRa\RaBundl...dureRepository::store() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
379
    }
380
381
    /**
382
     * @param string $procedureId
383
     * @return \Surfnet\StepupMiddlewareClient\Service\ExecutionResult
384
     * @throws UnknownVettingProcedureException
385
     * @throws DomainException
386
     */
387
    public function vet($procedureId)
388
    {
389
        $procedure = $this->getProcedure($procedureId);
390
        $procedure->vet();
391
392
        $command = new VetSecondFactorCommand();
393
        $command->authorityId = $procedure->getAuthorityId();
394
        $command->identityId = $procedure->getSecondFactor()->identityId;
395
        $command->secondFactorId = $procedure->getSecondFactor()->id;
396
        $command->registrationCode = $procedure->getRegistrationCode();
397
        $command->secondFactorType = $procedure->getSecondFactor()->type;
398
        $command->secondFactorIdentifier = $procedure->getInputSecondFactorIdentifier();
399
        $command->documentNumber = $procedure->getDocumentNumber();
400
        $command->identityVerified = $procedure->isIdentityVerified();
401
402
        $result = $this->commandService->execute($command);
403
404
        if ($result->isSuccessful()) {
405
            $this->vettingProcedureRepository->remove($procedureId);
406
        }
407
408
        return $result;
409
    }
410
411
    /**
412
     * @param string $procedureId
413
     * @return string
414
     * @throws UnknownVettingProcedureException
415
     */
416
    public function getIdentityCommonName($procedureId)
417
    {
418
        return $this->getProcedure($procedureId)->getSecondFactor()->commonName;
419
    }
420
421
    /**
422
     * @param $procedureId
423
     * @return string
424
     * @throws UnknownVettingProcedureException
425
     */
426
    public function getSecondFactorIdentifier($procedureId)
427
    {
428
        return $this->getProcedure($procedureId)->getSecondFactor()->secondFactorIdentifier;
429
    }
430
431
    /**
432
     * @param string $procedureId
433
     * @return null|VettingProcedure
434
     * @throws UnknownVettingProcedureException
435
     */
436 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...
437
    {
438
        if (!is_string($procedureId)) {
439
            throw InvalidArgumentException::invalidType('string', 'procedureId', $procedureId);
440
        }
441
442
        $procedure = $this->vettingProcedureRepository->retrieve($procedureId);
443
444
        if (!$procedure) {
445
            throw new UnknownVettingProcedureException(
446
                sprintf("No vetting procedure with id '%s' is known.", $procedureId)
447
            );
448
        }
449
450
        return $procedure;
451
    }
452
453
    /**
454
     * @param string $procedureId
455
     * @return bool
456
     */
457
    public function hasProcedure($procedureId)
458
    {
459
        if (!is_string($procedureId)) {
460
            throw InvalidArgumentException::invalidType('string', 'procedureId', $procedureId);
461
        }
462
463
        return $this->vettingProcedureRepository->retrieve($procedureId) !== null;
464
    }
465
}
466