Completed
Push — master ( e96383...13486f )
by
unknown
06:11
created

verifyU2fAuthenticationAction()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 62
rs 8.9167
c 0
b 0
f 0
cc 4
eloc 40
nc 4
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Saml\ResponseContext;
34
use Surfnet\StepupGateway\U2fVerificationBundle\Value\KeyHandle;
35
use Surfnet\StepupU2fBundle\Dto\SignResponse;
36
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
37
use Symfony\Component\Form\Form;
38
use Symfony\Component\Form\FormError;
39
use Symfony\Component\HttpFoundation\RedirectResponse;
40
use Symfony\Component\HttpFoundation\Request;
41
use Symfony\Component\HttpFoundation\Response;
42
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
43
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
44
45
/**
46
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
47
 */
48
class SecondFactorController extends Controller
49
{
50
    public function selectSecondFactorForVerificationAction()
51
    {
52
        $context = $this->getResponseContext();
53
        $originalRequestId = $context->getInResponseTo();
54
55
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
56
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
57
        $logger->notice('Determining which second factor to use...');
58
59
        try {
60
            // Retrieve all requirements to determine the required LoA
61
            $requestedLoa = $context->getRequiredLoa();
62
            $spConfiguredLoas = $context->getServiceProvider()->get('configuredLoas');
63
64
            $normalizedIdpSho = $context->getNormalizedSchacHomeOrganization();
65
            $normalizedUserSho = $this->getStepupService()->getNormalizedUserShoByIdentityNameId($context->getIdentityNameId());
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 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...
66
67
            $requiredLoa = $this
68
                ->getStepupService()
69
                ->resolveHighestRequiredLoa(
70
                    $requestedLoa,
71
                    $spConfiguredLoas,
72
                    $normalizedIdpSho,
73
                    $normalizedUserSho
74
                );
75
        } catch (LoaCannotBeGivenException $e) {
76
            // Log the message of the domain exception, this contains a meaningful message.
77
            $logger->notice($e->getMessage());
78
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
79
        }
80
81
        $logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa));
82
83
        if ($this->getStepupService()->isIntrinsicLoa($requiredLoa)) {
84
            $this->get('gateway.authentication_logger')->logIntrinsicLoaAuthentication($originalRequestId);
85
86
            return $this->forward($context->getResponseAction());
87
        }
88
89
        $secondFactorCollection = $this
90
            ->getStepupService()
91
            ->determineViableSecondFactors(
92
                $context->getIdentityNameId(),
93
                $requiredLoa,
94
                $this->get('gateway.service.whitelist')
95
            );
96
97
        switch (count($secondFactorCollection)) {
98
            case 0:
99
                $logger->notice('No second factors can give the determined Loa');
100
                return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
101
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
102
103
            case 1:
104
                $secondFactor = $secondFactorCollection->first();
105
                $logger->notice(sprintf(
106
                    'Found "%d" second factors, using second factor of type "%s"',
107
                    count($secondFactorCollection),
108
                    $secondFactor->secondFactorType
109
                ));
110
111
                return $this->selectAndRedirectTo($secondFactor, $context);
112
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
113
114
            default:
115
                return $this->forward(
116
                    'SurfnetStepupGatewayGatewayBundle:SecondFactor:chooseSecondFactor',
117
                    ['secondFactors' => $secondFactorCollection]
118
                );
119
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
120
        }
121
    }
122
123
    /**
124
     * @Template
125
     * @param Request $request
126
     * @return array|RedirectResponse|Response
127
     */
128
    public function chooseSecondFactorAction(Request $request)
129
    {
130
        $context = $this->getResponseContext();
131
        $originalRequestId = $context->getInResponseTo();
132
133
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
134
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
135
        $logger->notice('Ask the user which one of his suitable second factor tokens to use...');
136
137
        try {
138
            // Retrieve all requirements to determine the required LoA
139
            $requestedLoa = $context->getRequiredLoa();
140
            $spConfiguredLoas = $context->getServiceProvider()->get('configuredLoas');
141
142
            $normalizedIdpSho = $context->getNormalizedSchacHomeOrganization();
143
            $normalizedUserSho = $this->getStepupService()->getNormalizedUserShoByIdentityNameId($context->getIdentityNameId());
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 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...
144
145
            $requiredLoa = $this
146
                ->getStepupService()
147
                ->resolveHighestRequiredLoa(
148
                    $requestedLoa,
149
                    $spConfiguredLoas,
150
                    $normalizedIdpSho,
151
                    $normalizedUserSho
152
                );
153
        } catch (LoaCannotBeGivenException $e) {
154
            // Log the message of the domain exception, this contains a meaningful message.
155
            $logger->notice($e->getMessage());
156
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
157
        }
158
159
        $logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa));
160
161
        $secondFactors = $this
162
            ->getStepupService()
163
            ->determineViableSecondFactors(
164
                $context->getIdentityNameId(),
165
                $requiredLoa,
166
                $this->get('gateway.service.whitelist')
167
            );
168
169
        $command = new ChooseSecondFactorCommand();
170
        $command->secondFactors = $secondFactors;
0 ignored issues
show
Documentation Bug introduced by
It seems like $secondFactors of type object<Doctrine\Common\Collections\Collection> is incompatible with the declared type array<integer,object<Sur...e\Entity\SecondFactor>> of property $secondFactors.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
171
172
        $form = $this
173
            ->createForm(
174
                'gateway_choose_second_factor',
175
                $command,
176
                ['action' => $this->generateUrl('gateway_verify_second_factor_choose_second_factor')]
177
            )
178
            ->handleRequest($request);
179
180
        if ($form->isSubmitted() && $form->isValid()) {
181
            $buttonName = $form->getClickedButton()->getName();
182
            $formResults = $request->request->get('gateway_choose_second_factor', false);
183
184
            if (!isset($formResults[$buttonName])) {
185
                throw new InvalidArgumentException(
186
                    sprintf(
187
                        'Second factor type "%s" could not be found in the posted form results.',
188
                        $buttonName
189
                    )
190
                );
191
            }
192
193
            $secondFactorType = $formResults[$buttonName];
194
195
            // Filter the selected second factor from the array collection
196
            $secondFactorFiltered = $secondFactors->filter(
197
                function ($secondFactor) use ($secondFactorType) {
198
                    return $secondFactorType === $secondFactor->secondFactorType;
199
                }
200
            );
201
202
            if ($secondFactorFiltered->isEmpty()) {
203
                throw new InvalidArgumentException(
204
                    sprintf(
205
                        'Second factor type "%s" could not be found in the collection of available second factors.',
206
                        $secondFactorType
207
                    )
208
                );
209
            }
210
211
            $secondFactor = $secondFactorFiltered->first();
212
213
            $logger->notice(sprintf('User chose "%s" to use as second factor', $secondFactorType));
214
215
            // Forward to action to verify possession of second factor
216
            return $this->selectAndRedirectTo($secondFactor, $context);
217
        } else if ($form->isSubmitted() && !$form->isValid()) {
218
            $form->addError(new FormError('gateway.form.gateway_choose_second_factor.unknown_second_factor_type'));
219
        }
220
221
        return [
222
            'form' => $form->createView(),
223
            'secondFactors' => $secondFactors,
224
        ];
225
    }
226
227
    public function verifyGssfAction()
228
    {
229
        $context = $this->getResponseContext();
230
        $originalRequestId = $context->getInResponseTo();
231
232
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
233
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
234
        $logger->info('Received request to verify GSSF');
235
236
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
237
238
        $logger->info(sprintf(
239
            'Selected GSSF "%s" for verfication, forwarding to Saml handling',
240
            $selectedSecondFactor
241
        ));
242
243
        /** @var \Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService $secondFactorService */
244
        $secondFactorService = $this->get('gateway.service.second_factor_service');
245
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
246
        $secondFactor = $secondFactorService->findByUuid($selectedSecondFactor);
247
        if (!$secondFactor) {
248
            throw new RuntimeException(sprintf(
249
                'Requested verification of GSSF "%s", however that Second Factor no longer exists',
250
                $selectedSecondFactor
251
            ));
252
        }
253
254
        return $this->forward(
255
            'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:sendSecondFactorVerificationAuthnRequest',
256
            [
257
                'provider' => $secondFactor->secondFactorType,
258
                'subjectNameId' => $secondFactor->secondFactorIdentifier
259
            ]
260
        );
261
    }
262
263
    public function gssfVerifiedAction()
264
    {
265
        $context = $this->getResponseContext();
266
        $originalRequestId = $context->getInResponseTo();
267
268
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
269
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
270
        $logger->info('Attempting to mark GSSF as verified');
271
272
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
273
274
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
275
        $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor);
276
        if (!$secondFactor) {
277
            throw new RuntimeException(
278
                sprintf(
279
                    'Verification of GSSF "%s" succeeded, however that Second Factor no longer exists',
280
                    $selectedSecondFactor
281
                )
282
            );
283
        }
284
285
        $context->markSecondFactorVerified();
286
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
287
288
        $logger->info(sprintf(
289
            'Marked GSSF "%s" as verified, forwarding to Gateway controller to respond',
290
            $selectedSecondFactor
291
        ));
292
293
        return $this->forward($context->getResponseAction());
294
    }
295
296
    /**
297
     * @Template
298
     * @param Request $request
299
     * @return array|Response
300
     */
301
    public function verifyYubiKeySecondFactorAction(Request $request)
302
    {
303
        $context = $this->getResponseContext();
304
        $originalRequestId = $context->getInResponseTo();
305
306
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
307
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
308
309
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
310
311
        $logger->notice('Verifying possession of Yubikey second factor');
312
313
        $command = new VerifyYubikeyOtpCommand();
314
        $command->secondFactorId = $selectedSecondFactor;
315
316
        $form = $this->createForm('gateway_verify_yubikey_otp', $command)->handleRequest($request);
317
        $cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request);
318
319
        if (!$form->isValid()) {
320
            // OTP field is rendered empty in the template.
321
            return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
322
        }
323
324
        $result = $this->getStepupService()->verifyYubikeyOtp($command);
325
326
        if ($result->didOtpVerificationFail()) {
327
            $form->addError(new FormError('gateway.form.verify_yubikey.otp_verification_failed'));
328
329
            // OTP field is rendered empty in the template.
330
            return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
331
        } elseif (!$result->didPublicIdMatch()) {
332
            $form->addError(new FormError('gateway.form.verify_yubikey.public_id_mismatch'));
333
334
            // OTP field is rendered empty in the template.
335
            return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
336
        }
337
338
        $this->getResponseContext()->markSecondFactorVerified();
339
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
340
341
        $logger->info(
342
            sprintf(
343
                'Marked Yubikey Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
344
                $selectedSecondFactor
345
            )
346
        );
347
348
        return $this->forward($context->getResponseAction());
349
    }
350
351
    /**
352
     * @Template
353
     * @param Request $request
354
     * @return array|Response
355
     */
356
    public function verifySmsSecondFactorAction(Request $request)
357
    {
358
        $context = $this->getResponseContext();
359
        $originalRequestId = $context->getInResponseTo();
360
361
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
362
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
363
364
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
365
366
        $logger->notice('Verifying possession of SMS second factor, preparing to send');
367
368
        $command = new SendSmsChallengeCommand();
369
        $command->secondFactorId = $selectedSecondFactor;
370
371
        $form = $this->createForm('gateway_send_sms_challenge', $command)->handleRequest($request);
372
        $cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request);
373
374
        $stepupService = $this->getStepupService();
375
        $phoneNumber = InternationalPhoneNumber::fromStringFormat(
376
            $stepupService->getSecondFactorIdentifier($selectedSecondFactor)
377
        );
378
379
        $otpRequestsRemaining = $stepupService->getSmsOtpRequestsRemainingCount();
380
        $maximumOtpRequests = $stepupService->getSmsMaximumOtpRequestsCount();
381
        $viewVariables = ['otpRequestsRemaining' => $otpRequestsRemaining, 'maximumOtpRequests' => $maximumOtpRequests];
382
383
        if (!$form->isValid()) {
384
            return array_merge(
385
                $viewVariables,
386
                ['phoneNumber' => $phoneNumber, 'form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 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...
387
            );
388
        }
389
390
        $logger->notice('Verifying possession of SMS second factor, sending challenge per SMS');
391
392
        if (!$stepupService->sendSmsChallenge($command)) {
393
            $form->addError(new FormError('gateway.form.send_sms_challenge.sms_sending_failed'));
394
395
            return array_merge(
396
                $viewVariables,
397
                ['phoneNumber' => $phoneNumber, 'form' => $form->createView(), 'cancelForm' => $cancelForm->createView()]
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 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...
398
            );
399
        }
400
401
        return $this->redirect($this->generateUrl('gateway_verify_second_factor_sms_verify_challenge'));
402
    }
403
404
    /**
405
     * @Template
406
     * @param Request $request
407
     * @return array|Response
408
     */
409
    public function verifySmsSecondFactorChallengeAction(Request $request)
410
    {
411
        $context = $this->getResponseContext();
412
        $originalRequestId = $context->getInResponseTo();
413
414
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
415
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
416
417
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
418
419
        $command = new VerifyPossessionOfPhoneCommand();
420
        $form = $this->createForm('gateway_verify_sms_challenge', $command)->handleRequest($request);
421
        $cancelForm = $this->buildCancelAuthenticationForm()->handleRequest($request);
422
423
        if (!$form->isValid()) {
424
            return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
425
        }
426
427
        $logger->notice('Verifying input SMS challenge matches');
428
429
        $verification = $this->getStepupService()->verifySmsChallenge($command);
430
431
        if ($verification->wasSuccessful()) {
432
            $this->getStepupService()->clearSmsVerificationState();
433
434
            $this->getResponseContext()->markSecondFactorVerified();
435
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
436
437
            $logger->info(
438
                sprintf(
439
                    'Marked Sms Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
440
                    $selectedSecondFactor
441
                )
442
            );
443
444
            return $this->forward($context->getResponseAction());
445
        } elseif ($verification->didOtpExpire()) {
446
            $logger->notice('SMS challenge expired');
447
            $form->addError(new FormError('gateway.form.send_sms_challenge.challenge_expired'));
448
        } elseif ($verification->wasAttemptedTooManyTimes()) {
449
            $logger->notice('SMS challenge verification was attempted too many times');
450
            $form->addError(new FormError('gateway.form.send_sms_challenge.too_many_attempts'));
451
        } else {
452
            $logger->notice('SMS challenge did not match');
453
            $form->addError(new FormError('gateway.form.send_sms_challenge.sms_challenge_incorrect'));
454
        }
455
456
        return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
457
    }
458
459
    /**
460
     * @Template
461
     */
462
    public function initiateU2fAuthenticationAction()
463
    {
464
        $context = $this->getResponseContext();
465
        $originalRequestId = $context->getInResponseTo();
466
467
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
468
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
469
470
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
471
        $stepupService = $this->getStepupService();
472
473
        $cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication');
474
        $cancelForm =
475
            $this->createForm('gateway_cancel_second_factor_verification', null, ['action' => $cancelFormAction]);
476
477
        $logger->notice('Verifying possession of U2F second factor, looking for registration matching key handle');
478
479
        $service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification');
480
        $keyHandle = new KeyHandle($stepupService->getSecondFactorIdentifier($selectedSecondFactor));
481
        $registration = $service->findRegistrationByKeyHandle($keyHandle);
482
483
        if ($registration === null) {
484
            $logger->critical(
485
                sprintf('No known registration for key handle of second factor "%s"', $selectedSecondFactor)
486
            );
487
            $this->addFlash('error', 'gateway.u2f.alert.unknown_registration');
488
489
            return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
490
        }
491
492
        $logger->notice('Creating sign request');
493
494
        $signRequest = $service->createSignRequest($registration);
495
        $signResponse = new SignResponse();
496
497
        /** @var AttributeBagInterface $session */
498
        $session = $this->get('gateway.session.u2f');
499
        $session->set('request', $signRequest);
500
501
        $formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication');
502
        $form = $this->createForm(
503
            'surfnet_stepup_u2f_verify_device_authentication',
504
            $signResponse,
505
            ['sign_request' => $signRequest, 'action' => $formAction]
506
        );
507
508
        return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
509
    }
510
511
    /**
512
     * @Template("SurfnetStepupGatewayGatewayBundle:SecondFactor:initiateU2fAuthentication.html.twig")
513
     *
514
     * @param Request $request
515
     * @return array|Response
516
     */
517
    public function verifyU2fAuthenticationAction(Request $request)
518
    {
519
        $context = $this->getResponseContext();
520
        $originalRequestId = $context->getInResponseTo();
521
522
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
523
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
524
525
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
526
527
        $logger->notice('Received sign response from device');
528
529
        /** @var AttributeBagInterface $session */
530
        $session = $this->get('gateway.session.u2f');
531
        $signRequest = $session->get('request');
532
        $signResponse = new SignResponse();
533
534
        $formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication');
535
        $form = $this
536
            ->createForm(
537
                'surfnet_stepup_u2f_verify_device_authentication',
538
                $signResponse,
539
                ['sign_request' => $signRequest, 'action' => $formAction]
540
            )
541
            ->handleRequest($request);
542
543
        $cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication');
544
        $cancelForm =
545
            $this->createForm('gateway_cancel_second_factor_verification', null, ['action' => $cancelFormAction]);
546
547
        if (!$form->isValid()) {
548
            $logger->error('U2F authentication verification could not be started because device send illegal data');
549
            $this->addFlash('error', 'gateway.u2f.alert.error');
550
551
            return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
552
        }
553
554
        $service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification');
555
        $result = $service->verifyAuthentication($signRequest, $signResponse);
556
557
        if ($result->wasSuccessful()) {
558
            $context->markSecondFactorVerified();
559
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
560
561
            $logger->info(
562
                sprintf(
563
                    'Marked U2F second factor "%s" as verified, forwarding to Saml Proxy to respond',
564
                    $selectedSecondFactor
565
                )
566
            );
567
568
            return $this->forward($context->getResponseAction());
569
        } elseif ($result->didDeviceReportError()) {
570
            $logger->error('U2F device reported error during authentication');
571
            $this->addFlash('error', 'gateway.u2f.alert.device_reported_an_error');
572
        } else {
573
            $logger->error('U2F authentication verification failed');
574
            $this->addFlash('error', 'gateway.u2f.alert.error');
575
        }
576
577
        return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
578
    }
579
580
    public function cancelAuthenticationAction()
581
    {
582
        return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
583
    }
584
585
    /**
586
     * @return \Surfnet\StepupGateway\GatewayBundle\Service\StepupAuthenticationService
587
     */
588
    private function getStepupService()
589
    {
590
        return $this->get('gateway.service.stepup_authentication');
591
    }
592
593
    /**
594
     * @return ResponseContext
595
     */
596
    private function getResponseContext()
597
    {
598
        return $this->get($this->get('gateway.proxy.state_handler')->getResponseContextServiceId());
599
    }
600
601
    /**
602
     * @return \Surfnet\StepupGateway\GatewayBundle\Monolog\Logger\AuthenticationLogger
603
     */
604
    private function getAuthenticationLogger()
605
    {
606
        return $this->get('gateway.authentication_logger');
607
    }
608
609
    /**
610
     * @param ResponseContext $context
611
     * @param LoggerInterface $logger
612
     * @return string
613
     */
614
    private function getSelectedSecondFactor(ResponseContext $context, LoggerInterface $logger)
615
    {
616
        $selectedSecondFactor = $context->getSelectedSecondFactor();
617
618
        if (!$selectedSecondFactor) {
619
            $logger->error('Cannot verify possession of an unknown second factor');
620
621
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
622
        }
623
624
        return $selectedSecondFactor;
625
    }
626
627
    private function selectAndRedirectTo(SecondFactor $secondFactor, ResponseContext $context)
628
    {
629
        $context->saveSelectedSecondFactor($secondFactor);
630
631
        $this->getStepupService()->clearSmsVerificationState();
632
633
        $secondFactorTypeService = $this->get('surfnet_stepup.service.second_factor_type');
634
        $secondFactorType = new SecondFactorType($secondFactor->secondFactorType);
635
636
        $route = 'gateway_verify_second_factor_';
637
        if ($secondFactorTypeService->isGssf($secondFactorType)) {
638
            $route .= 'gssf';
639
        } else {
640
            $route .= strtolower($secondFactor->secondFactorType);
641
        }
642
643
        return $this->redirect($this->generateUrl($route));
644
    }
645
646
    /**
647
     * @return Form
648
     */
649
    private function buildCancelAuthenticationForm()
650
    {
651
        $cancelFormAction = $this->generateUrl('gateway_cancel_authentication');
652
        $cancelForm = $this->createForm(
653
            'gateway_cancel_authentication',
654
            null,
655
            ['action' => $cancelFormAction]
656
        );
657
        return $cancelForm;
658
    }
659
}
660