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\StepupGateway\GatewayBundle\Controller; |
20
|
|
|
|
21
|
|
|
use Psr\Log\LoggerInterface; |
22
|
|
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; |
23
|
|
|
use Surfnet\StepupBundle\Command\VerifyPossessionOfPhoneCommand; |
24
|
|
|
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber; |
25
|
|
|
use Surfnet\StepupBundle\Value\SecondFactorType; |
26
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Command\ChooseSecondFactorCommand; |
27
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Command\SendSmsChallengeCommand; |
28
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Command\VerifyYubikeyOtpCommand; |
29
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor; |
30
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Exception\InvalidArgumentException; |
31
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Exception\LoaCannotBeGivenException; |
32
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException; |
33
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Form\Type\CancelAuthenticationType; |
34
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Form\Type\CancelSecondFactorVerificationType; |
35
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Form\Type\ChooseSecondFactorType; |
36
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Form\Type\SendSmsChallengeType; |
37
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Form\Type\VerifySmsChallengeType; |
38
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Form\Type\VerifyYubikeyOtpType; |
39
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext; |
40
|
|
|
use Surfnet\StepupGateway\U2fVerificationBundle\Value\KeyHandle; |
41
|
|
|
use Surfnet\StepupU2fBundle\Dto\SignResponse; |
42
|
|
|
use Surfnet\StepupU2fBundle\Form\Type\VerifyDeviceAuthenticationType; |
43
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\Controller; |
44
|
|
|
use Symfony\Component\Form\Form; |
45
|
|
|
use Symfony\Component\Form\FormError; |
46
|
|
|
use Symfony\Component\HttpFoundation\RedirectResponse; |
47
|
|
|
use Symfony\Component\HttpFoundation\Request; |
48
|
|
|
use Symfony\Component\HttpFoundation\Response; |
49
|
|
|
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; |
50
|
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
54
|
|
|
*/ |
55
|
|
|
class SecondFactorController extends Controller |
|
|
|
|
56
|
|
|
{ |
57
|
|
|
public function selectSecondFactorForVerificationAction() |
58
|
|
|
{ |
59
|
|
|
$context = $this->getResponseContext(); |
60
|
|
|
$originalRequestId = $context->getInResponseTo(); |
61
|
|
|
|
62
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
63
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
64
|
|
|
$logger->notice('Determining which second factor to use...'); |
65
|
|
|
|
66
|
|
|
try { |
67
|
|
|
// Retrieve all requirements to determine the required LoA |
68
|
|
|
$requestedLoa = $context->getRequiredLoa(); |
69
|
|
|
$spConfiguredLoas = $context->getServiceProvider()->get('configuredLoas'); |
70
|
|
|
|
71
|
|
|
$normalizedIdpSho = $context->getNormalizedSchacHomeOrganization(); |
72
|
|
|
$normalizedUserSho = $this->getStepupService()->getNormalizedUserShoByIdentityNameId($context->getIdentityNameId()); |
73
|
|
|
|
74
|
|
|
$requiredLoa = $this |
75
|
|
|
->getStepupService() |
76
|
|
|
->resolveHighestRequiredLoa( |
77
|
|
|
$requestedLoa, |
78
|
|
|
$spConfiguredLoas, |
79
|
|
|
$normalizedIdpSho, |
80
|
|
|
$normalizedUserSho |
81
|
|
|
); |
82
|
|
|
} catch (LoaCannotBeGivenException $e) { |
83
|
|
|
// Log the message of the domain exception, this contains a meaningful message. |
84
|
|
|
$logger->notice($e->getMessage()); |
85
|
|
|
return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven'); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
$logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa)); |
89
|
|
|
|
90
|
|
|
if ($this->getStepupService()->isIntrinsicLoa($requiredLoa)) { |
91
|
|
|
$this->get('gateway.authentication_logger')->logIntrinsicLoaAuthentication($originalRequestId); |
92
|
|
|
|
93
|
|
|
return $this->forward($context->getResponseAction()); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$secondFactorCollection = $this |
97
|
|
|
->getStepupService() |
98
|
|
|
->determineViableSecondFactors( |
99
|
|
|
$context->getIdentityNameId(), |
100
|
|
|
$requiredLoa, |
101
|
|
|
$this->get('gateway.service.whitelist') |
102
|
|
|
); |
103
|
|
|
|
104
|
|
|
switch (count($secondFactorCollection)) { |
105
|
|
|
case 0: |
106
|
|
|
$logger->notice('No second factors can give the determined Loa'); |
107
|
|
|
return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven'); |
108
|
|
|
break; |
|
|
|
|
109
|
|
|
|
110
|
|
|
case 1: |
111
|
|
|
$secondFactor = $secondFactorCollection->first(); |
112
|
|
|
$logger->notice(sprintf( |
113
|
|
|
'Found "%d" second factors, using second factor of type "%s"', |
114
|
|
|
count($secondFactorCollection), |
115
|
|
|
$secondFactor->secondFactorType |
116
|
|
|
)); |
117
|
|
|
|
118
|
|
|
return $this->selectAndRedirectTo($secondFactor, $context); |
119
|
|
|
break; |
|
|
|
|
120
|
|
|
|
121
|
|
|
default: |
122
|
|
|
return $this->forward( |
123
|
|
|
'SurfnetStepupGatewayGatewayBundle:SecondFactor:chooseSecondFactor', |
124
|
|
|
['secondFactors' => $secondFactorCollection] |
125
|
|
|
); |
126
|
|
|
break; |
|
|
|
|
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @Template |
132
|
|
|
* @param Request $request |
133
|
|
|
* @return array|RedirectResponse|Response |
134
|
|
|
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) |
135
|
|
|
*/ |
136
|
|
|
public function chooseSecondFactorAction(Request $request) |
137
|
|
|
{ |
138
|
|
|
$context = $this->getResponseContext(); |
139
|
|
|
$originalRequestId = $context->getInResponseTo(); |
140
|
|
|
|
141
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
142
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
143
|
|
|
$logger->notice('Ask the user which one of his suitable second factor tokens to use...'); |
144
|
|
|
|
145
|
|
|
try { |
146
|
|
|
// Retrieve all requirements to determine the required LoA |
147
|
|
|
$requestedLoa = $context->getRequiredLoa(); |
148
|
|
|
$spConfiguredLoas = $context->getServiceProvider()->get('configuredLoas'); |
149
|
|
|
|
150
|
|
|
$normalizedIdpSho = $context->getNormalizedSchacHomeOrganization(); |
151
|
|
|
$normalizedUserSho = $this->getStepupService()->getNormalizedUserShoByIdentityNameId($context->getIdentityNameId()); |
152
|
|
|
|
153
|
|
|
$requiredLoa = $this |
154
|
|
|
->getStepupService() |
155
|
|
|
->resolveHighestRequiredLoa( |
156
|
|
|
$requestedLoa, |
157
|
|
|
$spConfiguredLoas, |
158
|
|
|
$normalizedIdpSho, |
159
|
|
|
$normalizedUserSho |
160
|
|
|
); |
161
|
|
|
} catch (LoaCannotBeGivenException $e) { |
162
|
|
|
// Log the message of the domain exception, this contains a meaningful message. |
163
|
|
|
$logger->notice($e->getMessage()); |
164
|
|
|
return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven'); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa)); |
168
|
|
|
|
169
|
|
|
$secondFactors = $this |
170
|
|
|
->getStepupService() |
171
|
|
|
->determineViableSecondFactors( |
172
|
|
|
$context->getIdentityNameId(), |
173
|
|
|
$requiredLoa, |
174
|
|
|
$this->get('gateway.service.whitelist') |
175
|
|
|
); |
176
|
|
|
|
177
|
|
|
$command = new ChooseSecondFactorCommand(); |
178
|
|
|
$command->secondFactors = $secondFactors; |
|
|
|
|
179
|
|
|
|
180
|
|
|
$form = $this |
181
|
|
|
->createForm( |
182
|
|
|
ChooseSecondFactorType::class, |
183
|
|
|
$command, |
184
|
|
|
['action' => $this->generateUrl('gateway_verify_second_factor_choose_second_factor')] |
185
|
|
|
) |
186
|
|
|
->handleRequest($request); |
187
|
|
|
$cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request); |
188
|
|
|
|
189
|
|
|
if ($form->isSubmitted() && $form->isValid()) { |
190
|
|
|
$buttonName = $form->getClickedButton()->getName(); |
191
|
|
|
$formResults = $request->request->get('gateway_choose_second_factor', false); |
192
|
|
|
|
193
|
|
|
if (!isset($formResults[$buttonName])) { |
194
|
|
|
throw new InvalidArgumentException( |
195
|
|
|
sprintf( |
196
|
|
|
'Second factor type "%s" could not be found in the posted form results.', |
197
|
|
|
$buttonName |
198
|
|
|
) |
199
|
|
|
); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
$secondFactorType = $formResults[$buttonName]; |
203
|
|
|
|
204
|
|
|
// Filter the selected second factor from the array collection |
205
|
|
|
$secondFactorFiltered = $secondFactors->filter( |
206
|
|
|
function ($secondFactor) use ($secondFactorType) { |
207
|
|
|
return $secondFactorType === $secondFactor->secondFactorType; |
208
|
|
|
} |
209
|
|
|
); |
210
|
|
|
|
211
|
|
|
if ($secondFactorFiltered->isEmpty()) { |
212
|
|
|
throw new InvalidArgumentException( |
213
|
|
|
sprintf( |
214
|
|
|
'Second factor type "%s" could not be found in the collection of available second factors.', |
215
|
|
|
$secondFactorType |
216
|
|
|
) |
217
|
|
|
); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
$secondFactor = $secondFactorFiltered->first(); |
221
|
|
|
|
222
|
|
|
$logger->notice(sprintf('User chose "%s" to use as second factor', $secondFactorType)); |
223
|
|
|
|
224
|
|
|
// Forward to action to verify possession of second factor |
225
|
|
|
return $this->selectAndRedirectTo($secondFactor, $context); |
226
|
|
|
} else if ($form->isSubmitted() && !$form->isValid()) { |
227
|
|
|
$form->addError(new FormError('gateway.form.gateway_choose_second_factor.unknown_second_factor_type')); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return [ |
231
|
|
|
'form' => $form->createView(), |
232
|
|
|
'cancelForm' => $cancelForm->createView(), |
233
|
|
|
'secondFactors' => $secondFactors, |
234
|
|
|
]; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
public function verifyGssfAction() |
238
|
|
|
{ |
239
|
|
|
$context = $this->getResponseContext(); |
240
|
|
|
$originalRequestId = $context->getInResponseTo(); |
241
|
|
|
|
242
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
243
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
244
|
|
|
$logger->info('Received request to verify GSSF'); |
245
|
|
|
|
246
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
247
|
|
|
|
248
|
|
|
$logger->info(sprintf( |
249
|
|
|
'Selected GSSF "%s" for verfication, forwarding to Saml handling', |
250
|
|
|
$selectedSecondFactor |
251
|
|
|
)); |
252
|
|
|
|
253
|
|
|
/** @var \Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService $secondFactorService */ |
254
|
|
|
$secondFactorService = $this->get('gateway.service.second_factor_service'); |
255
|
|
|
/** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */ |
256
|
|
|
$secondFactor = $secondFactorService->findByUuid($selectedSecondFactor); |
257
|
|
|
if (!$secondFactor) { |
258
|
|
|
throw new RuntimeException(sprintf( |
259
|
|
|
'Requested verification of GSSF "%s", however that Second Factor no longer exists', |
260
|
|
|
$selectedSecondFactor |
261
|
|
|
)); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
// Also send the response context service id, as later we need to know if this is regular SSO or SFO authn. |
265
|
|
|
$responseContextServiceId = $context->getResponseContextServiceId(); |
266
|
|
|
|
267
|
|
|
return $this->forward( |
268
|
|
|
'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:sendSecondFactorVerificationAuthnRequest', |
269
|
|
|
[ |
270
|
|
|
'provider' => $secondFactor->secondFactorType, |
271
|
|
|
'subjectNameId' => $secondFactor->secondFactorIdentifier, |
272
|
|
|
'responseContextServiceId' => $responseContextServiceId, |
273
|
|
|
] |
274
|
|
|
); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
public function gssfVerifiedAction() |
278
|
|
|
{ |
279
|
|
|
$context = $this->getResponseContext(); |
280
|
|
|
$originalRequestId = $context->getInResponseTo(); |
281
|
|
|
|
282
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
283
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
284
|
|
|
$logger->info('Attempting to mark GSSF as verified'); |
285
|
|
|
|
286
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
287
|
|
|
|
288
|
|
|
/** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */ |
289
|
|
|
$secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor); |
290
|
|
|
if (!$secondFactor) { |
291
|
|
|
throw new RuntimeException( |
292
|
|
|
sprintf( |
293
|
|
|
'Verification of GSSF "%s" succeeded, however that Second Factor no longer exists', |
294
|
|
|
$selectedSecondFactor |
295
|
|
|
) |
296
|
|
|
); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
$context->markSecondFactorVerified(); |
300
|
|
|
$this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId); |
301
|
|
|
|
302
|
|
|
$logger->info(sprintf( |
303
|
|
|
'Marked GSSF "%s" as verified, forwarding to Gateway controller to respond', |
304
|
|
|
$selectedSecondFactor |
305
|
|
|
)); |
306
|
|
|
|
307
|
|
|
return $this->forward($context->getResponseAction()); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @Template |
312
|
|
|
* @param Request $request |
313
|
|
|
* @return array|Response |
314
|
|
|
*/ |
315
|
|
|
public function verifyYubiKeySecondFactorAction(Request $request) |
316
|
|
|
{ |
317
|
|
|
$context = $this->getResponseContext(); |
318
|
|
|
$originalRequestId = $context->getInResponseTo(); |
319
|
|
|
|
320
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
321
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
322
|
|
|
|
323
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
324
|
|
|
|
325
|
|
|
$logger->notice('Verifying possession of Yubikey second factor'); |
326
|
|
|
|
327
|
|
|
$command = new VerifyYubikeyOtpCommand(); |
328
|
|
|
$command->secondFactorId = $selectedSecondFactor; |
329
|
|
|
|
330
|
|
|
$form = $this->createForm(VerifyYubikeyOtpType::class, $command)->handleRequest($request); |
331
|
|
|
$cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request); |
332
|
|
|
|
333
|
|
View Code Duplication |
if ($form->isSubmitted() && !$form->isValid()) { |
|
|
|
|
334
|
|
|
// OTP field is rendered empty in the template. |
335
|
|
|
return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
$result = $this->getStepupService()->verifyYubikeyOtp($command); |
339
|
|
|
|
340
|
|
|
if ($result->didOtpVerificationFail()) { |
341
|
|
|
$form->addError(new FormError('gateway.form.verify_yubikey.otp_verification_failed')); |
342
|
|
|
|
343
|
|
|
// OTP field is rendered empty in the template. |
344
|
|
|
return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]; |
345
|
|
|
} elseif (!$result->didPublicIdMatch()) { |
346
|
|
|
$form->addError(new FormError('gateway.form.verify_yubikey.public_id_mismatch')); |
347
|
|
|
|
348
|
|
|
// OTP field is rendered empty in the template. |
349
|
|
|
return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
$this->getResponseContext()->markSecondFactorVerified(); |
353
|
|
|
$this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId); |
354
|
|
|
|
355
|
|
|
$logger->info( |
356
|
|
|
sprintf( |
357
|
|
|
'Marked Yubikey Second Factor "%s" as verified, forwarding to Saml Proxy to respond', |
358
|
|
|
$selectedSecondFactor |
359
|
|
|
) |
360
|
|
|
); |
361
|
|
|
|
362
|
|
|
return $this->forward($context->getResponseAction()); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* @Template |
367
|
|
|
* @param Request $request |
368
|
|
|
* @return array|Response |
369
|
|
|
*/ |
370
|
|
|
public function verifySmsSecondFactorAction(Request $request) |
371
|
|
|
{ |
372
|
|
|
$context = $this->getResponseContext(); |
373
|
|
|
$originalRequestId = $context->getInResponseTo(); |
374
|
|
|
|
375
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
376
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
377
|
|
|
|
378
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
379
|
|
|
|
380
|
|
|
$logger->notice('Verifying possession of SMS second factor, preparing to send'); |
381
|
|
|
|
382
|
|
|
$command = new SendSmsChallengeCommand(); |
383
|
|
|
$command->secondFactorId = $selectedSecondFactor; |
384
|
|
|
|
385
|
|
|
$form = $this->createForm(SendSmsChallengeType::class, $command)->handleRequest($request); |
386
|
|
|
$cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request); |
387
|
|
|
|
388
|
|
|
$stepupService = $this->getStepupService(); |
389
|
|
|
$phoneNumber = InternationalPhoneNumber::fromStringFormat( |
390
|
|
|
$stepupService->getSecondFactorIdentifier($selectedSecondFactor) |
391
|
|
|
); |
392
|
|
|
|
393
|
|
|
$otpRequestsRemaining = $stepupService->getSmsOtpRequestsRemainingCount(); |
394
|
|
|
$maximumOtpRequests = $stepupService->getSmsMaximumOtpRequestsCount(); |
395
|
|
|
$viewVariables = ['otpRequestsRemaining' => $otpRequestsRemaining, 'maximumOtpRequests' => $maximumOtpRequests]; |
396
|
|
|
|
397
|
|
|
if ($form->isSubmitted() && !$form->isValid()) { |
398
|
|
|
return array_merge( |
399
|
|
|
$viewVariables, |
400
|
|
|
['phoneNumber' => $phoneNumber, 'form' => $form->createView(), 'cancelForm' => $cancelForm->createView()] |
401
|
|
|
); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
$logger->notice('Verifying possession of SMS second factor, sending challenge per SMS'); |
405
|
|
|
|
406
|
|
|
if (!$stepupService->sendSmsChallenge($command)) { |
407
|
|
|
$form->addError(new FormError('gateway.form.send_sms_challenge.sms_sending_failed')); |
408
|
|
|
|
409
|
|
|
return array_merge( |
410
|
|
|
$viewVariables, |
411
|
|
|
['phoneNumber' => $phoneNumber, 'form' => $form->createView(), 'cancelForm' => $cancelForm->createView()] |
412
|
|
|
); |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
return $this->redirect($this->generateUrl('gateway_verify_second_factor_sms_verify_challenge')); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* @Template |
420
|
|
|
* @param Request $request |
421
|
|
|
* @return array|Response |
422
|
|
|
*/ |
423
|
|
|
public function verifySmsSecondFactorChallengeAction(Request $request) |
424
|
|
|
{ |
425
|
|
|
$context = $this->getResponseContext(); |
426
|
|
|
$originalRequestId = $context->getInResponseTo(); |
427
|
|
|
|
428
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
429
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
430
|
|
|
|
431
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
432
|
|
|
|
433
|
|
|
$command = new VerifyPossessionOfPhoneCommand(); |
434
|
|
|
$form = $this->createForm(VerifySmsChallengeType::class, $command)->handleRequest($request); |
435
|
|
|
$cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request); |
436
|
|
|
|
437
|
|
View Code Duplication |
if ($form->isSubmitted() && !$form->isValid()) { |
|
|
|
|
438
|
|
|
return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
$logger->notice('Verifying input SMS challenge matches'); |
442
|
|
|
|
443
|
|
|
$verification = $this->getStepupService()->verifySmsChallenge($command); |
444
|
|
|
|
445
|
|
|
if ($verification->wasSuccessful()) { |
446
|
|
|
$this->getStepupService()->clearSmsVerificationState(); |
447
|
|
|
|
448
|
|
|
$this->getResponseContext()->markSecondFactorVerified(); |
449
|
|
|
$this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId); |
450
|
|
|
|
451
|
|
|
$logger->info( |
452
|
|
|
sprintf( |
453
|
|
|
'Marked Sms Second Factor "%s" as verified, forwarding to Saml Proxy to respond', |
454
|
|
|
$selectedSecondFactor |
455
|
|
|
) |
456
|
|
|
); |
457
|
|
|
|
458
|
|
|
return $this->forward($context->getResponseAction()); |
459
|
|
|
} elseif ($verification->didOtpExpire()) { |
460
|
|
|
$logger->notice('SMS challenge expired'); |
461
|
|
|
$form->addError(new FormError('gateway.form.send_sms_challenge.challenge_expired')); |
462
|
|
|
} elseif ($verification->wasAttemptedTooManyTimes()) { |
463
|
|
|
$logger->notice('SMS challenge verification was attempted too many times'); |
464
|
|
|
$form->addError(new FormError('gateway.form.send_sms_challenge.too_many_attempts')); |
465
|
|
|
} else { |
466
|
|
|
$logger->notice('SMS challenge did not match'); |
467
|
|
|
$form->addError(new FormError('gateway.form.send_sms_challenge.sms_challenge_incorrect')); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]; |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* @Template |
475
|
|
|
*/ |
476
|
|
|
public function initiateU2fAuthenticationAction() |
477
|
|
|
{ |
478
|
|
|
$context = $this->getResponseContext(); |
479
|
|
|
$originalRequestId = $context->getInResponseTo(); |
480
|
|
|
|
481
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
482
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
483
|
|
|
|
484
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
485
|
|
|
$stepupService = $this->getStepupService(); |
486
|
|
|
|
487
|
|
|
$cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication'); |
488
|
|
|
$cancelForm = |
489
|
|
|
$this->createForm(CancelSecondFactorVerificationType::class, null, ['action' => $cancelFormAction]); |
490
|
|
|
|
491
|
|
|
$logger->notice('Verifying possession of U2F second factor, looking for registration matching key handle'); |
492
|
|
|
|
493
|
|
|
$service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification'); |
494
|
|
|
$keyHandle = new KeyHandle($stepupService->getSecondFactorIdentifier($selectedSecondFactor)); |
495
|
|
|
$registration = $service->findRegistrationByKeyHandle($keyHandle); |
496
|
|
|
|
497
|
|
|
if ($registration === null) { |
498
|
|
|
$logger->critical( |
499
|
|
|
sprintf('No known registration for key handle of second factor "%s"', $selectedSecondFactor) |
500
|
|
|
); |
501
|
|
|
$this->addFlash('error', 'gateway.u2f.alert.unknown_registration'); |
502
|
|
|
|
503
|
|
|
return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()]; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
$logger->notice('Creating sign request'); |
507
|
|
|
|
508
|
|
|
$signRequest = $service->createSignRequest($registration); |
509
|
|
|
$signResponse = new SignResponse(); |
510
|
|
|
|
511
|
|
|
/** @var AttributeBagInterface $session */ |
512
|
|
|
$session = $this->get('gateway.session.u2f'); |
513
|
|
|
$session->set('request', $signRequest); |
514
|
|
|
|
515
|
|
|
$formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication'); |
516
|
|
|
$form = $this->createForm( |
517
|
|
|
VerifyDeviceAuthenticationType::class, |
518
|
|
|
$signResponse, |
519
|
|
|
['sign_request' => $signRequest, 'action' => $formAction] |
520
|
|
|
); |
521
|
|
|
|
522
|
|
|
return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* @Template("SurfnetStepupGatewayGatewayBundle:SecondFactor:initiateU2fAuthentication.html.twig") |
527
|
|
|
* |
528
|
|
|
* @param Request $request |
529
|
|
|
* @return array|Response |
530
|
|
|
*/ |
531
|
|
|
public function verifyU2fAuthenticationAction(Request $request) |
532
|
|
|
{ |
533
|
|
|
$context = $this->getResponseContext(); |
534
|
|
|
$originalRequestId = $context->getInResponseTo(); |
535
|
|
|
|
536
|
|
|
/** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */ |
537
|
|
|
$logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId); |
538
|
|
|
|
539
|
|
|
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger); |
540
|
|
|
|
541
|
|
|
$logger->notice('Received sign response from device'); |
542
|
|
|
|
543
|
|
|
/** @var AttributeBagInterface $session */ |
544
|
|
|
$session = $this->get('gateway.session.u2f'); |
545
|
|
|
$signRequest = $session->get('request'); |
546
|
|
|
$signResponse = new SignResponse(); |
547
|
|
|
|
548
|
|
|
$formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication'); |
549
|
|
|
$form = $this |
550
|
|
|
->createForm( |
551
|
|
|
VerifyDeviceAuthenticationType::class, |
552
|
|
|
$signResponse, |
553
|
|
|
['sign_request' => $signRequest, 'action' => $formAction] |
554
|
|
|
) |
555
|
|
|
->handleRequest($request); |
556
|
|
|
|
557
|
|
|
$cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication'); |
558
|
|
|
$cancelForm = |
559
|
|
|
$this->createForm(CancelSecondFactorVerificationType::class, null, ['action' => $cancelFormAction]); |
560
|
|
|
|
561
|
|
|
if ($form->isSubmitted() && !$form->isValid()) { |
562
|
|
|
$logger->error('U2F authentication verification could not be started because device send illegal data'); |
563
|
|
|
$this->addFlash('error', 'gateway.u2f.alert.error'); |
564
|
|
|
|
565
|
|
|
return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()]; |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
$service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification'); |
569
|
|
|
$result = $service->verifyAuthentication($signRequest, $signResponse); |
570
|
|
|
|
571
|
|
|
if ($result->wasSuccessful()) { |
572
|
|
|
$context->markSecondFactorVerified(); |
573
|
|
|
$this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId); |
574
|
|
|
|
575
|
|
|
$logger->info( |
576
|
|
|
sprintf( |
577
|
|
|
'Marked U2F second factor "%s" as verified, forwarding to Saml Proxy to respond', |
578
|
|
|
$selectedSecondFactor |
579
|
|
|
) |
580
|
|
|
); |
581
|
|
|
|
582
|
|
|
return $this->forward($context->getResponseAction()); |
583
|
|
|
} elseif ($result->didDeviceReportError()) { |
584
|
|
|
$logger->error('U2F device reported error during authentication'); |
585
|
|
|
$this->addFlash('error', 'gateway.u2f.alert.device_reported_an_error'); |
586
|
|
|
} else { |
587
|
|
|
$logger->error('U2F authentication verification failed'); |
588
|
|
|
$this->addFlash('error', 'gateway.u2f.alert.error'); |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()]; |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
public function cancelAuthenticationAction() |
595
|
|
|
{ |
596
|
|
|
return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser'); |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
/** |
600
|
|
|
* @return \Surfnet\StepupGateway\GatewayBundle\Service\StepupAuthenticationService |
601
|
|
|
*/ |
602
|
|
|
private function getStepupService() |
603
|
|
|
{ |
604
|
|
|
return $this->get('gateway.service.stepup_authentication'); |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
/** |
608
|
|
|
* @return ResponseContext |
609
|
|
|
*/ |
610
|
|
|
private function getResponseContext() |
611
|
|
|
{ |
612
|
|
|
return $this->get($this->get('gateway.proxy.state_handler')->getResponseContextServiceId()); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* @return \Surfnet\StepupGateway\GatewayBundle\Monolog\Logger\AuthenticationLogger |
617
|
|
|
*/ |
618
|
|
|
private function getAuthenticationLogger() |
619
|
|
|
{ |
620
|
|
|
return $this->get('gateway.authentication_logger'); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* @param ResponseContext $context |
625
|
|
|
* @param LoggerInterface $logger |
626
|
|
|
* @return string |
627
|
|
|
*/ |
628
|
|
|
private function getSelectedSecondFactor(ResponseContext $context, LoggerInterface $logger) |
629
|
|
|
{ |
630
|
|
|
$selectedSecondFactor = $context->getSelectedSecondFactor(); |
631
|
|
|
|
632
|
|
|
if (!$selectedSecondFactor) { |
633
|
|
|
$logger->error('Cannot verify possession of an unknown second factor'); |
634
|
|
|
|
635
|
|
|
throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.'); |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
return $selectedSecondFactor; |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
private function selectAndRedirectTo(SecondFactor $secondFactor, ResponseContext $context) |
642
|
|
|
{ |
643
|
|
|
$context->saveSelectedSecondFactor($secondFactor); |
644
|
|
|
|
645
|
|
|
$this->getStepupService()->clearSmsVerificationState(); |
646
|
|
|
|
647
|
|
|
$secondFactorTypeService = $this->get('surfnet_stepup.service.second_factor_type'); |
648
|
|
|
$secondFactorType = new SecondFactorType($secondFactor->secondFactorType); |
649
|
|
|
|
650
|
|
|
$route = 'gateway_verify_second_factor_'; |
651
|
|
|
if ($secondFactorTypeService->isGssf($secondFactorType)) { |
652
|
|
|
$route .= 'gssf'; |
653
|
|
|
} else { |
654
|
|
|
$route .= strtolower($secondFactor->secondFactorType); |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
return $this->redirect($this->generateUrl($route)); |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
/** |
661
|
|
|
* @return Form |
662
|
|
|
*/ |
663
|
|
|
private function buildCancelAuthenticationForm() |
664
|
|
|
{ |
665
|
|
|
$cancelFormAction = $this->generateUrl('gateway_cancel_authentication'); |
666
|
|
|
$cancelForm = $this->createForm( |
667
|
|
|
CancelAuthenticationType::class, |
668
|
|
|
null, |
669
|
|
|
['action' => $cancelFormAction] |
670
|
|
|
); |
671
|
|
|
return $cancelForm; |
672
|
|
|
} |
673
|
|
|
} |
674
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.