Passed
Push — develop ( c85f88...7c450d )
by Peter
14:03
created

tokenCreatedDuringSecondFactorRegistration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright 2022 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\StepupSelfService\SelfServiceBundle\Service;
20
21
use Surfnet\StepupBundle\Command\SendRecoveryTokenSmsChallengeCommand as StepupSendRecoveryTokenSmsChallengeCommand;
22
use Surfnet\StepupBundle\Command\VerifyPossessionOfPhoneForRecoveryTokenCommand;
23
use Surfnet\StepupBundle\Service\Exception\TooManyChallengesRequestedException;
24
use Surfnet\StepupBundle\Service\SmsRecoveryTokenService as StepupSmsRecoveryTokenService;
25
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber;
26
use Surfnet\StepupBundle\Value\PhoneNumber\PhoneNumber;
27
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\ProvePhoneRecoveryTokenPossessionCommand;
28
use Surfnet\StepupMiddlewareClientBundle\Uuid\Uuid;
29
use Surfnet\StepupSelfService\SelfServiceBundle\Command\SendRecoveryTokenSmsAuthenticationChallengeCommand;
30
use Surfnet\StepupSelfService\SelfServiceBundle\Command\SendRecoveryTokenSmsChallengeCommand;
31
use Surfnet\StepupSelfService\SelfServiceBundle\Command\VerifySmsRecoveryTokenChallengeCommand;
32
use Surfnet\StepupSelfService\SelfServiceBundle\Service\SelfAssertedTokens\ProofOfPossessionResult;
33
use Surfnet\StepupSelfService\SelfServiceBundle\Service\SelfAssertedTokens\RecoveryTokenState;
34
use Symfony\Component\Translation\TranslatorInterface;
35
36
/**
37
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
38
 */
39
class SmsRecoveryTokenService
40
{
41
    public const REGISTRATION_RECOVERY_TOKEN_ID = 'registration';
42
43
    private $smsService;
44
45
    private $translator;
46
47
    private $commandService;
48
49
    private $stateHandler;
50
51
    public function __construct(
52
        StepupSmsRecoveryTokenService $smsService,
53
        TranslatorInterface $translator,
54
        CommandService $commandService,
55
        RecoveryTokenState $stateHandler
56
    ) {
57
        $this->smsService = $smsService;
58
        $this->translator = $translator;
59
        $this->commandService = $commandService;
60
        $this->stateHandler = $stateHandler;
61
    }
62
63
    public function getOtpRequestsRemainingCount(string $identifier): int
64
    {
65
        return $this->smsService->getOtpRequestsRemainingCount($identifier);
66
    }
67
68
    public function getMaximumOtpRequestsCount(): int
69
    {
70
        return $this->smsService->getMaximumOtpRequestsCount();
71
    }
72
73
    public function hasSmsVerificationState(string $secondFactorId): bool
74
    {
75
        return $this->smsService->hasSmsVerificationState($secondFactorId);
76
    }
77
78
    public function clearSmsVerificationState(string $secondFactorId): void
79
    {
80
        $this->smsService->clearSmsVerificationState($secondFactorId);
81
    }
82
83
    public function authenticate(SendRecoveryTokenSmsAuthenticationChallengeCommand $command): bool
84
    {
85
        $stepupCommand = new StepupSendRecoveryTokenSmsChallengeCommand();
86
        $stepupCommand->phoneNumber = $command->identifier;
87
        $stepupCommand->body = $this->translator->trans('ss.registration.sms.challenge_body');
88
        $stepupCommand->identity = $command->identity;
89
        $stepupCommand->institution = $command->institution;
90
        $stepupCommand->recoveryTokenId = $command->recoveryTokenId;
91
92
        return $this->smsService->sendChallenge($stepupCommand);
93
    }
94
95
    /**
96
     * @return bool Whether SMS sending did not fail.
97
     * @throws TooManyChallengesRequestedException
98
     */
99
    public function sendChallenge(SendRecoveryTokenSmsChallengeCommand $command): bool
100
    {
101
        $phoneNumber = new InternationalPhoneNumber(
102
            $command->country->getCountryCode(),
103
            new PhoneNumber($command->subscriber)
104
        );
105
106
        $stepupCommand = new StepupSendRecoveryTokenSmsChallengeCommand();
107
        $stepupCommand->phoneNumber = $phoneNumber;
108
        $stepupCommand->body = $this->translator->trans('ss.registration.sms.challenge_body');
109
        $stepupCommand->identity = $command->identity;
110
        $stepupCommand->institution = $command->institution;
111
        $stepupCommand->recoveryTokenId = $command->recoveryTokenId;
112
113
        return $this->smsService->sendChallenge($stepupCommand);
114
    }
115
116
    public function provePossession(VerifySmsRecoveryTokenChallengeCommand $challengeCommand): ProofOfPossessionResult
117
    {
118
        $stepupCommand = new VerifyPossessionOfPhoneForRecoveryTokenCommand();
119
        $stepupCommand->challenge = $challengeCommand->challenge;
120
        $stepupCommand->recoveryTokenId = $challengeCommand->recoveryTokenId;
121
122
        $verification = $this->smsService->verifyPossession($stepupCommand);
123
124
        if ($verification->didOtpExpire()) {
125
            return ProofOfPossessionResult::challengeExpired();
126
        }
127
        if ($verification->wasAttemptedTooManyTimes()) {
128
            return ProofOfPossessionResult::tooManyAttempts();
129
        }
130
        if (!$verification->wasSuccessful()) {
131
            return ProofOfPossessionResult::incorrectChallenge();
132
        }
133
134
        $command = new ProvePhoneRecoveryTokenPossessionCommand();
135
        $command->identityId = $challengeCommand->identity;
136
        $command->recoveryTokenId = Uuid::generate();
137
        $command->phoneNumber = $verification->getPhoneNumber();
138
139
        $result = $this->commandService->execute($command);
140
141
        if (!$result->isSuccessful()) {
142
            return ProofOfPossessionResult::proofOfPossessionCommandFailed();
143
        }
144
145
        return ProofOfPossessionResult::recoveryTokenCreated($command->recoveryTokenId);
146
    }
147
148
    public function wasTokenCreatedDuringSecondFactorRegistration()
149
    {
150
        return $this->stateHandler->wasRecoveryTokenCreatedDuringSecondFactorRegistration();
151
    }
152
153
    public function forgetTokenCreatedDuringSecondFactorRegistration()
154
    {
155
        $this->stateHandler->forgetTokenCreatedDuringSecondFactorRegistration();
156
    }
157
158
    public function tokenCreatedDuringSecondFactorRegistration()
159
    {
160
        $this->stateHandler->tokenCreatedDuringSecondFactorRegistration();
161
    }
162
163
    public function forgetRecoveryTokenState()
164
    {
165
        $this->stateHandler->forget();
166
    }
167
168
    public function verifyAuthentication(VerifySmsRecoveryTokenChallengeCommand $command): ProofOfPossessionResult
169
    {
170
        $stepupCommand = new VerifyPossessionOfPhoneForRecoveryTokenCommand();
171
        $stepupCommand->challenge = $command->challenge;
172
        $stepupCommand->recoveryTokenId = $command->recoveryTokenId;
173
174
        $verification = $this->smsService->verifyPossession($stepupCommand);
175
176
        if ($verification->didOtpExpire()) {
177
            return ProofOfPossessionResult::challengeExpired();
178
        }
179
        if ($verification->wasAttemptedTooManyTimes()) {
180
            return ProofOfPossessionResult::tooManyAttempts();
181
        }
182
        if (!$verification->wasSuccessful()) {
183
            return ProofOfPossessionResult::incorrectChallenge();
184
        }
185
186
        return ProofOfPossessionResult::recoveryTokenVerified();
187
    }
188
}
189