1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Copyright 2014 SURFnet bv |
7
|
|
|
* |
8
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
9
|
|
|
* you may not use this file except in compliance with the License. |
10
|
|
|
* You may obtain a copy of the License at |
11
|
|
|
* |
12
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
13
|
|
|
* |
14
|
|
|
* Unless required by applicable law or agreed to in writing, software |
15
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
16
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
17
|
|
|
* See the License for the specific language governing permissions and |
18
|
|
|
* limitations under the License. |
19
|
|
|
*/ |
|
|
|
|
20
|
|
|
|
21
|
|
|
namespace Surfnet\StepupSelfService\SelfServiceBundle\Service; |
22
|
|
|
|
23
|
|
|
use Surfnet\StepupMiddlewareClient\Identity\Dto\UnverifiedSecondFactorSearchQuery; |
24
|
|
|
use Surfnet\StepupMiddlewareClient\Identity\Dto\VerifiedSecondFactorOfIdentitySearchQuery; |
25
|
|
|
use Surfnet\StepupMiddlewareClient\Identity\Dto\VettedSecondFactorSearchQuery; |
26
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Dto\CollectionDto; |
27
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\RegisterSelfAssertedSecondFactorCommand; |
28
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\SelfVetSecondFactorCommand; |
29
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\RevokeOwnSecondFactorCommand; |
30
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Command\VerifyEmailCommand; |
31
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\UnverifiedSecondFactor; |
32
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\UnverifiedSecondFactorCollection; |
33
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VerifiedSecondFactor; |
34
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VerifiedSecondFactorCollection; |
35
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VettedSecondFactor; |
36
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\VettedSecondFactorCollection; |
37
|
|
|
use Surfnet\StepupMiddlewareClientBundle\Identity\Service\SecondFactorService as MiddlewareSecondFactorService; |
38
|
|
|
use Surfnet\StepupSelfService\SelfServiceBundle\Command\SelfAssertedTokenRegistrationCommand; |
39
|
|
|
use Surfnet\StepupSelfService\SelfServiceBundle\Command\SelfVetCommand; |
40
|
|
|
use Surfnet\StepupSelfService\SelfServiceBundle\Command\RevokeCommand; |
41
|
|
|
use Surfnet\StepupSelfService\SelfServiceBundle\Exception\LogicException; |
42
|
|
|
|
43
|
|
|
/** |
|
|
|
|
44
|
|
|
* @SuppressWarnings(PHPMD.TooManyPublicMethods) |
45
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
46
|
|
|
*/ |
|
|
|
|
47
|
|
|
class SecondFactorService |
48
|
|
|
{ |
49
|
|
|
public function __construct(private readonly MiddlewareSecondFactorService $secondFactors, private readonly CommandService $commandService) |
|
|
|
|
50
|
|
|
{ |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function verifyEmail(string $identityId, string $nonce): bool |
|
|
|
|
54
|
|
|
{ |
55
|
|
|
$command = new VerifyEmailCommand(); |
56
|
|
|
$command->identityId = $identityId; |
57
|
|
|
$command->verificationNonce = $nonce; |
58
|
|
|
|
59
|
|
|
$result = $this->commandService->execute($command); |
60
|
|
|
|
61
|
|
|
return $result->isSuccessful(); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function revoke(RevokeCommand $command): bool |
|
|
|
|
65
|
|
|
{ |
66
|
|
|
/** @var UnverifiedSecondFactor|VerifiedSecondFactor|VettedSecondFactor $secondFactor */ |
|
|
|
|
67
|
|
|
$secondFactor = $command->secondFactor; |
68
|
|
|
|
69
|
|
|
$apiCommand = new RevokeOwnSecondFactorCommand(); |
70
|
|
|
$apiCommand->identityId = $command->identity->id; |
71
|
|
|
$apiCommand->secondFactorId = $secondFactor->id; |
72
|
|
|
|
73
|
|
|
$result = $this->commandService->execute($apiCommand); |
74
|
|
|
|
75
|
|
|
return $result->isSuccessful(); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function selfVet(SelfVetCommand $command): bool |
|
|
|
|
79
|
|
|
{ |
80
|
|
|
$apiCommand = new SelfVetSecondFactorCommand(); |
81
|
|
|
$apiCommand->identityId = $command->identity->id; |
82
|
|
|
$apiCommand->registrationCode = $command->secondFactor->registrationCode; |
83
|
|
|
$apiCommand->secondFactorIdentifier = $command->secondFactor->id; |
84
|
|
|
$apiCommand->secondFactorId = $command->secondFactor->secondFactorIdentifier; |
85
|
|
|
$apiCommand->secondFactorType = $command->secondFactor->type; |
86
|
|
|
$apiCommand->authorityId = $command->identity->id; |
87
|
|
|
$apiCommand->authoringSecondFactorIdentifier = $command->authoringLoa; |
88
|
|
|
|
89
|
|
|
$result = $this->commandService->execute($apiCommand); |
90
|
|
|
return $result->isSuccessful(); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
public function registerSelfAssertedToken(SelfAssertedTokenRegistrationCommand $command): bool |
|
|
|
|
94
|
|
|
{ |
95
|
|
|
$apiCommand = new RegisterSelfAssertedSecondFactorCommand(); |
96
|
|
|
$apiCommand->identityId = $command->identity->id; |
97
|
|
|
$apiCommand->registrationCode = $command->secondFactor->registrationCode; |
98
|
|
|
$apiCommand->secondFactorIdentifier = $command->secondFactor->secondFactorIdentifier; |
99
|
|
|
$apiCommand->secondFactorId = $command->secondFactor->id; |
100
|
|
|
$apiCommand->secondFactorType = $command->secondFactor->type; |
101
|
|
|
$apiCommand->authorityId = $command->identity->id; |
102
|
|
|
$apiCommand->authoringRecoveryTokenId = $command->recoveryTokenId; |
103
|
|
|
|
104
|
|
|
return $this->commandService->execute($apiCommand)->isSuccessful(); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
|
|
|
|
108
|
|
|
* Returns whether the given registrant has registered second factors with Step-up. The state of the second factor |
109
|
|
|
* is irrelevant. |
110
|
|
|
*/ |
|
|
|
|
111
|
|
|
public function doSecondFactorsExistForIdentity(string $identityId): bool |
112
|
|
|
{ |
113
|
|
|
$unverifiedSecondFactors = $this->findUnverifiedByIdentity($identityId); |
114
|
|
|
$verifiedSecondFactors = $this->findVerifiedByIdentity($identityId); |
115
|
|
|
$vettedSecondFactors = $this->findVettedByIdentity($identityId); |
116
|
|
|
|
117
|
|
|
return $unverifiedSecondFactors->getTotalItems() + |
118
|
|
|
$verifiedSecondFactors->getTotalItems() + |
119
|
|
|
$vettedSecondFactors->getTotalItems() > 0; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
|
|
|
|
123
|
|
|
* Returns the given registrant's unverified second factors. |
124
|
|
|
*/ |
|
|
|
|
125
|
|
|
public function findUnverifiedByIdentity(string $identityId): ?UnverifiedSecondFactorCollection |
126
|
|
|
{ |
127
|
|
|
return $this->secondFactors->searchUnverified( |
128
|
|
|
(new UnverifiedSecondFactorSearchQuery())->setIdentityId($identityId), |
129
|
|
|
); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
|
|
|
|
133
|
|
|
* Returns the given registrant's verified second factors. |
134
|
|
|
*/ |
|
|
|
|
135
|
|
|
public function findVerifiedByIdentity(string $identityId): ?VerifiedSecondFactorCollection |
136
|
|
|
{ |
137
|
|
|
$query = new VerifiedSecondFactorOfIdentitySearchQuery(); |
138
|
|
|
$query->setIdentityId($identityId); |
139
|
|
|
return $this->secondFactors->searchOwnVerified($query); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
|
|
|
|
143
|
|
|
* Returns the given registrant's verified second factors. |
144
|
|
|
*/ |
|
|
|
|
145
|
|
|
public function findVettedByIdentity(string $identityId): ?VettedSecondFactorCollection |
146
|
|
|
{ |
147
|
|
|
return $this->secondFactors->searchVetted( |
148
|
|
|
(new VettedSecondFactorSearchQuery())->setIdentityId($identityId), |
149
|
|
|
); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
public function identityHasSecondFactorOfStateWithId(string $identityId, $state, $secondFactorId): bool |
|
|
|
|
153
|
|
|
{ |
154
|
|
|
$secondFactors = match ($state) { |
155
|
|
|
'unverified' => $this->findUnverifiedByIdentity($identityId), |
156
|
|
|
'verified' => $this->findVerifiedByIdentity($identityId), |
157
|
|
|
'vetted' => $this->findVettedByIdentity($identityId), |
158
|
|
|
default => throw new LogicException(sprintf('Invalid second factor state "%s" given.', $state)), |
159
|
|
|
}; |
160
|
|
|
|
161
|
|
|
if ($secondFactors->getElements() === []) { |
162
|
|
|
return false; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
foreach ($secondFactors->getElements() as $secondFactor) { |
166
|
|
|
if ($secondFactor->id === $secondFactorId) { |
167
|
|
|
return true; |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return false; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
public function findOneUnverified(string $secondFactorId): ?UnverifiedSecondFactor |
|
|
|
|
175
|
|
|
{ |
176
|
|
|
return $this->secondFactors->getUnverified($secondFactorId); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
public function findOneVerified(string $secondFactorId): ?VerifiedSecondFactor |
|
|
|
|
180
|
|
|
{ |
181
|
|
|
return $this->secondFactors->getVerified($secondFactorId); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
public function findOneVetted(string $secondFactorId): ?VettedSecondFactor |
|
|
|
|
185
|
|
|
{ |
186
|
|
|
return $this->secondFactors->getVetted($secondFactorId); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
public function findUnverifiedByVerificationNonce(string $identityId, string $verificationNonce): ?UnverifiedSecondFactor |
|
|
|
|
190
|
|
|
{ |
191
|
|
|
$secondFactors = $this->secondFactors->searchUnverified( |
192
|
|
|
(new UnverifiedSecondFactorSearchQuery()) |
193
|
|
|
->setIdentityId($identityId) |
194
|
|
|
->setVerificationNonce($verificationNonce), |
195
|
|
|
); |
196
|
|
|
|
197
|
|
|
$elements = $secondFactors->getElements(); |
198
|
|
|
|
199
|
|
|
return match (count($elements)) { |
200
|
|
|
0 => null, |
201
|
|
|
1 => reset($elements), |
202
|
|
|
default => throw new LogicException('There cannot be more than one unverified second factor with the same nonce'), |
203
|
|
|
}; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
|
|
|
|
207
|
|
|
* @param $identity |
|
|
|
|
208
|
|
|
* @param array $allSecondFactors |
|
|
|
|
209
|
|
|
* @param $allowedSecondFactors |
|
|
|
|
210
|
|
|
* @param $maximumNumberOfRegistrations |
|
|
|
|
211
|
|
|
* @return SecondFactorTypeCollection |
|
|
|
|
212
|
|
|
*/ |
213
|
|
|
public function getSecondFactorsForIdentity( |
214
|
|
|
$identity, |
215
|
|
|
array $allSecondFactors, |
216
|
|
|
$allowedSecondFactors, |
217
|
|
|
$maximumNumberOfRegistrations, |
218
|
|
|
): SecondFactorTypeCollection { |
219
|
|
|
$unverified = $this->findUnverifiedByIdentity($identity->id); |
220
|
|
|
$verified = $this->findVerifiedByIdentity($identity->id); |
221
|
|
|
$vetted = $this->findVettedByIdentity($identity->id); |
222
|
|
|
// Determine which Second Factors are still available for registration. |
223
|
|
|
$available = $this->determineAvailable($allSecondFactors, $unverified, $verified, $vetted); |
|
|
|
|
224
|
|
|
|
225
|
|
|
if (!empty($allowedSecondFactors)) { |
226
|
|
|
$available = array_intersect( |
227
|
|
|
$available, |
228
|
|
|
$allowedSecondFactors, |
229
|
|
|
); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
$collection = new SecondFactorTypeCollection(); |
233
|
|
|
$collection->unverified = $unverified; |
234
|
|
|
$collection->verified = $verified; |
235
|
|
|
$collection->vetted = $vetted; |
236
|
|
|
$collection->available = array_combine($available, $available); |
237
|
|
|
$collection->maxNumberOfRegistrations = $maximumNumberOfRegistrations; |
238
|
|
|
|
239
|
|
|
return $collection; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
private function determineAvailable( |
|
|
|
|
243
|
|
|
array $allSecondFactors, |
244
|
|
|
UnverifiedSecondFactorCollection $unverifiedCollection, |
245
|
|
|
VerifiedSecondFactorCollection $verifiedCollection, |
246
|
|
|
VettedSecondFactorCollection $vettedCollection, |
247
|
|
|
): array { |
248
|
|
|
$allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $unverifiedCollection); |
249
|
|
|
$allSecondFactors = $this->filterAvailableSecondFactors($allSecondFactors, $verifiedCollection); |
250
|
|
|
return $this->filterAvailableSecondFactors($allSecondFactors, $vettedCollection); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
private function filterAvailableSecondFactors(array $allSecondFactors, CollectionDto $collection): array |
|
|
|
|
254
|
|
|
{ |
255
|
|
|
foreach ($collection->getElements() as $secondFactor) { |
256
|
|
|
$keyFound = array_search($secondFactor->type, $allSecondFactors); |
257
|
|
|
if (is_numeric($keyFound)) { |
258
|
|
|
unset($allSecondFactors[$keyFound]); |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
return $allSecondFactors; |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
|