Completed
Pull Request — develop (#101)
by Boy
06:04 queued 02:51
created

selectSecondFactorForVerificationAction()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 80
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 80
rs 8.4041
cc 6
eloc 46
nc 6
nop 0

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\StepupGateway\GatewayBundle\Command\SendSmsChallengeCommand;
26
use Surfnet\StepupGateway\GatewayBundle\Command\VerifyYubikeyOtpCommand;
27
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException;
28
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext;
29
use Surfnet\StepupGateway\U2fVerificationBundle\Value\KeyHandle;
30
use Surfnet\StepupU2fBundle\Dto\SignResponse;
31
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
32
use Symfony\Component\Form\FormError;
33
use Symfony\Component\HttpFoundation\Request;
34
use Symfony\Component\HttpFoundation\Response;
35
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
36
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
37
38
/**
39
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
40
 */
41
class SecondFactorController extends Controller
42
{
43
    public function selectSecondFactorForVerificationAction()
44
    {
45
        $context = $this->getResponseContext();
46
        $originalRequestId = $context->getInResponseTo();
47
48
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
49
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
50
        $logger->notice('Determining which second factor to use...');
51
52
        $requiredLoa = $this
53
            ->getStepupService()
54
            ->resolveHighestRequiredLoa(
55
                $context->getRequiredLoa(),
56
                $context->getServiceProvider(),
57
                $context->getAuthenticatingIdp()
58
            );
59
60
        if ($requiredLoa === null) {
61
            $logger->notice(
62
                'No valid required Loa can be determined, no authentication is possible, Loa cannot be given'
63
            );
64
65
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
66
        } else {
67
            $logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa));
68
        }
69
70
        if ($this->getStepupService()->isIntrinsicLoa($requiredLoa)) {
71
            $this->get('gateway.authentication_logger')->logIntrinsicLoaAuthentication($originalRequestId);
72
73
            return $this->forward($context->getResponseAction());
74
        }
75
76
        $secondFactorCollection = $this
77
            ->getStepupService()
78
            ->determineViableSecondFactors($context->getIdentityNameId(), $requiredLoa);
79
80
        if (count($secondFactorCollection) === 0) {
81
            $logger->notice('No second factors can give the determined Loa');
82
83
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
84
        }
85
86
        // will be replaced by a second factor selection screen once we support multiple
87
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
88
        $secondFactor = $secondFactorCollection->first();
89
        // when multiple second factors are supported this should be moved into the
90
        // StepUpAuthenticationService::determineViableSecondFactors and handled in a performant way
91
        // currently keeping this here for visibility
92
        if (!$this->get('gateway.service.whitelist')->contains($secondFactor->institution)) {
93
            $logger->notice(sprintf(
94
                'Second factor "%s" is listed for institution "%s" which is not on the whitelist, sending Loa '
95
                . 'cannot be given response',
96
                $secondFactor->secondFactorId,
97
                $secondFactor->institution
98
            ));
99
100
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
101
        }
102
103
        $logger->notice(sprintf(
104
            'Found "%d" second factors, using second factor of type "%s"',
105
            count($secondFactorCollection),
106
            $secondFactor->secondFactorType
107
        ));
108
109
        $context->saveSelectedSecondFactor($secondFactor);
110
111
        $this->getStepupService()->clearSmsVerificationState();
112
113
        $route = 'gateway_verify_second_factor_';
114
        if ($secondFactor->requiresGssf()) {
115
            $route .= 'gssf';
116
        }
117
        else {
118
            $route .= strtolower($secondFactor->secondFactorType);
119
        }
120
121
        return $this->redirect($this->generateUrl($route));
122
    }
123
124
    public function verifyGssfAction()
125
    {
126
        $context = $this->getResponseContext();
127
        $originalRequestId = $context->getInResponseTo();
128
129
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
130
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
131
        $logger->info('Received request to verify GSSF');
132
133
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
134
135
        $logger->info(sprintf(
136
            'Selected GSSF "%s" for verfication, forwarding to Saml handling',
137
            $selectedSecondFactor
138
        ));
139
140
        /** @var \Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService $secondFactorService */
141
        $secondFactorService = $this->get('gateway.service.second_factor_service');
142
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
143
        $secondFactor = $secondFactorService->findByUuid($selectedSecondFactor);
144
        if (!$secondFactor) {
145
            $logger->critical(sprintf(
146
                'Requested verification of GSSF "%s", however that Second Factor no longer exists',
147
                $selectedSecondFactor
148
            ));
149
150
            throw new RuntimeException('Verification of selected second factor that no longer exists');
151
        }
152
153
        return $this->forward(
154
            'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:sendSecondFactorVerificationAuthnRequest',
155
            [
156
                'provider' => $secondFactor->secondFactorType,
157
                'subjectNameId' => $secondFactor->secondFactorIdentifier
158
            ]
159
        );
160
    }
161
162
    public function gssfVerifiedAction()
163
    {
164
        $context = $this->getResponseContext();
165
        $originalRequestId = $context->getInResponseTo();
166
167
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
168
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
169
        $logger->info('Attempting to mark GSSF as verified');
170
171
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
172
173
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
174
        $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor);
175
        if (!$secondFactor) {
176
            $logger->critical(sprintf(
177
                'Verification of GSSF "%s" succeeded, however that Second Factor no longer exists',
178
                $selectedSecondFactor
179
            ));
180
181
            throw new RuntimeException('Verification of selected second factor that no longer exists');
182
        }
183
184
        $context->markSecondFactorVerified();
185
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
186
187
        $logger->info(sprintf(
188
            'Marked GSSF "%s" as verified, forwarding to Gateway controller to respond',
189
            $selectedSecondFactor
190
        ));
191
192
        return $this->forward($context->getResponseAction());
193
    }
194
195
    /**
196
     * @Template
197
     * @param Request $request
198
     * @return array|Response
199
     */
200
    public function verifyYubiKeySecondFactorAction(Request $request)
201
    {
202
        $context = $this->getResponseContext();
203
        $originalRequestId = $context->getInResponseTo();
204
205
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
206
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
207
208
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
209
210
        $logger->notice('Verifying possession of Yubikey second factor');
211
212
        $command = new VerifyYubikeyOtpCommand();
213
        $command->secondFactorId = $selectedSecondFactor;
214
215
        $form = $this->createForm('gateway_verify_yubikey_otp', $command)->handleRequest($request);
216
217
        if ($form->get('cancel')->isClicked()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Form\FormInterface as the method isClicked() does only exist in the following implementations of said interface: Symfony\Component\Form\SubmitButton.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
218
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
219
        }
220
221
        if (!$form->isValid()) {
222
            // OTP field is rendered empty in the template.
223
            return ['form' => $form->createView()];
224
        }
225
226
        $result = $this->getStepupService()->verifyYubikeyOtp($command);
227
228
        if ($result->didOtpVerificationFail()) {
229
            $form->addError(new FormError('gateway.form.verify_yubikey.otp_verification_failed'));
230
231
            // OTP field is rendered empty in the template.
232
            return ['form' => $form->createView()];
233
        } elseif (!$result->didPublicIdMatch()) {
234
            $form->addError(new FormError('gateway.form.verify_yubikey.public_id_mismatch'));
235
236
            // OTP field is rendered empty in the template.
237
            return ['form' => $form->createView()];
238
        }
239
240
        $this->getResponseContext()->markSecondFactorVerified();
241
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
242
243
        $logger->info(
244
            sprintf(
245
                'Marked Yubikey Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
246
                $selectedSecondFactor
247
            )
248
        );
249
250
        return $this->forward($context->getResponseAction());
251
    }
252
253
    /**
254
     * @Template
255
     * @param Request $request
256
     * @return array|Response
257
     */
258
    public function verifySmsSecondFactorAction(Request $request)
259
    {
260
        $context = $this->getResponseContext();
261
        $originalRequestId = $context->getInResponseTo();
262
263
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
264
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
265
266
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
267
268
        $logger->notice('Verifying possession of SMS second factor, preparing to send');
269
270
        $command = new SendSmsChallengeCommand();
271
        $command->secondFactorId = $selectedSecondFactor;
272
273
        $form = $this->createForm('gateway_send_sms_challenge', $command)->handleRequest($request);
274
275
        $stepupService = $this->getStepupService();
276
        $phoneNumber = InternationalPhoneNumber::fromStringFormat(
277
            $stepupService->getSecondFactorIdentifier($selectedSecondFactor)
278
        );
279
280
        $otpRequestsRemaining = $stepupService->getSmsOtpRequestsRemainingCount();
281
        $maximumOtpRequests = $stepupService->getSmsMaximumOtpRequestsCount();
282
        $viewVariables = ['otpRequestsRemaining' => $otpRequestsRemaining, 'maximumOtpRequests' => $maximumOtpRequests];
283
284
        if ($form->get('cancel')->isClicked()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Form\FormInterface as the method isClicked() does only exist in the following implementations of said interface: Symfony\Component\Form\SubmitButton.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
285
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
286
        }
287
288
        if (!$form->isValid()) {
289
            return array_merge($viewVariables, ['phoneNumber' => $phoneNumber, 'form' => $form->createView()]);
290
        }
291
292
        $logger->notice('Verifying possession of SMS second factor, sending challenge per SMS');
293
294
        if (!$stepupService->sendSmsChallenge($command)) {
295
            $form->addError(new FormError('gateway.form.send_sms_challenge.sms_sending_failed'));
296
297
            return array_merge($viewVariables, ['phoneNumber' => $phoneNumber, 'form' => $form->createView()]);
298
        }
299
300
        return $this->redirect($this->generateUrl('gateway_verify_second_factor_sms_verify_challenge'));
301
    }
302
303
    /**
304
     * @Template
305
     * @param Request $request
306
     * @return array|Response
307
     */
308
    public function verifySmsSecondFactorChallengeAction(Request $request)
309
    {
310
        $context = $this->getResponseContext();
311
        $originalRequestId = $context->getInResponseTo();
312
313
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
314
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
315
316
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
317
318
        $command = new VerifyPossessionOfPhoneCommand();
319
        $form = $this->createForm('gateway_verify_sms_challenge', $command)->handleRequest($request);
320
321
        if ($form->get('cancel')->isClicked()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Form\FormInterface as the method isClicked() does only exist in the following implementations of said interface: Symfony\Component\Form\SubmitButton.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
322
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
323
        }
324
325
        if (!$form->isValid()) {
326
            return ['form' => $form->createView()];
327
        }
328
329
        $logger->notice('Verifying input SMS challenge matches');
330
331
        $verification = $this->getStepupService()->verifySmsChallenge($command);
332
333
        if ($verification->wasSuccessful()) {
334
            $this->getStepupService()->clearSmsVerificationState();
335
336
            $this->getResponseContext()->markSecondFactorVerified();
337
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
338
339
            $logger->info(
340
                sprintf(
341
                    'Marked Sms Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
342
                    $selectedSecondFactor
343
                )
344
            );
345
346
            return $this->forward($context->getResponseAction());
347
        } elseif ($verification->didOtpExpire()) {
348
            $logger->notice('SMS challenge expired');
349
            $form->addError(new FormError('gateway.form.send_sms_challenge.challenge_expired'));
350
        } elseif ($verification->wasAttemptedTooManyTimes()) {
351
            $logger->notice('SMS challenge verification was attempted too many times');
352
            $form->addError(new FormError('gateway.form.send_sms_challenge.too_many_attempts'));
353
        } else {
354
            $logger->notice('SMS challenge did not match');
355
            $form->addError(new FormError('gateway.form.send_sms_challenge.sms_challenge_incorrect'));
356
        }
357
358
        return ['form' => $form->createView()];
359
    }
360
361
    /**
362
     * @Template
363
     */
364
    public function initiateU2fAuthenticationAction()
365
    {
366
        $context = $this->getResponseContext();
367
        $originalRequestId = $context->getInResponseTo();
368
369
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
370
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
371
372
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
373
        $stepupService = $this->getStepupService();
374
375
        $cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication');
376
        $cancelForm =
377
            $this->createForm('gateway_cancel_second_factor_verification', null, ['action' => $cancelFormAction]);
378
379
        $logger->notice('Verifying possession of U2F second factor, looking for registration matching key handle');
380
381
        $service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification');
382
        $keyHandle = new KeyHandle($stepupService->getSecondFactorIdentifier($selectedSecondFactor));
383
        $registration = $service->findRegistrationByKeyHandle($keyHandle);
384
385
        if ($registration === null) {
386
            $logger->critical(
387
                sprintf('No known registration for key handle of second factor "%s"', $selectedSecondFactor)
388
            );
389
            $this->addFlash('error', 'gateway.u2f.alert.unknown_registration');
390
391
            return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
392
        }
393
394
        $logger->notice('Creating sign request');
395
396
        $signRequest = $service->createSignRequest($registration);
397
        $signResponse = new SignResponse();
398
399
        /** @var AttributeBagInterface $session */
400
        $session = $this->get('gateway.session.u2f');
401
        $session->set('request', $signRequest);
402
403
        $formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication');
404
        $form = $this->createForm(
405
            'surfnet_stepup_u2f_verify_device_authentication',
406
            $signResponse,
407
            ['sign_request' => $signRequest, 'action' => $formAction]
408
        );
409
410
        return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
411
    }
412
413
    /**
414
     * @Template("SurfnetStepupGatewayGatewayBundle:SecondFactor:initiateU2fAuthentication.html.twig")
415
     *
416
     * @param Request $request
417
     * @return array|Response
418
     */
419
    public function verifyU2fAuthenticationAction(Request $request)
420
    {
421
        $context = $this->getResponseContext();
422
        $originalRequestId = $context->getInResponseTo();
423
424
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
425
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
426
427
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
428
429
        $logger->notice('Received sign response from device');
430
431
        /** @var AttributeBagInterface $session */
432
        $session = $this->get('gateway.session.u2f');
433
        $signRequest = $session->get('request');
434
        $signResponse = new SignResponse();
435
436
        $formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication');
437
        $form = $this
438
            ->createForm(
439
                'surfnet_stepup_u2f_verify_device_authentication',
440
                $signResponse,
441
                ['sign_request' => $signRequest, 'action' => $formAction]
442
            )
443
            ->handleRequest($request);
444
445
        $cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication');
446
        $cancelForm =
447
            $this->createForm('gateway_cancel_second_factor_verification', null, ['action' => $cancelFormAction]);
448
449
        if (!$form->isValid()) {
450
            $logger->error('U2F authentication verification could not be started because device send illegal data');
451
            $this->addFlash('error', 'gateway.u2f.alert.error');
452
453
            return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
454
        }
455
456
        $service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification');
457
        $result = $service->verifyAuthentication($signRequest, $signResponse);
458
459
        if ($result->wasSuccessful()) {
460
            $context->markSecondFactorVerified();
461
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
462
463
            $logger->info(
464
                sprintf(
465
                    'Marked U2F second factor "%s" as verified, forwarding to Saml Proxy to respond',
466
                    $selectedSecondFactor
467
                )
468
            );
469
470
            return $this->forward($context->getResponseAction());
471
        } elseif ($result->didDeviceReportError()) {
472
            $logger->error('U2F device reported error during authentication');
473
            $this->addFlash('error', 'gateway.u2f.alert.device_reported_an_error');
474
        } else {
475
            $logger->error('U2F authentication verification failed');
476
            $this->addFlash('error', 'gateway.u2f.alert.error');
477
        }
478
479
        return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
480
    }
481
482
    public function cancelU2fAuthenticationAction()
483
    {
484
        return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
485
    }
486
487
    /**
488
     * @return \Surfnet\StepupGateway\GatewayBundle\Service\StepupAuthenticationService
489
     */
490
    private function getStepupService()
491
    {
492
        return $this->get('gateway.service.stepup_authentication');
493
    }
494
495
    /**
496
     * @return ResponseContext
497
     */
498
    private function getResponseContext()
499
    {
500
        return $this->get($this->get('gateway.proxy.state_handler')->getResponseContextServiceId());
501
    }
502
503
    /**
504
     * @return \Surfnet\StepupGateway\GatewayBundle\Monolog\Logger\AuthenticationLogger
505
     */
506
    private function getAuthenticationLogger()
507
    {
508
        return $this->get('gateway.authentication_logger');
509
    }
510
511
    /**
512
     * @param ResponseContext $context
513
     * @param LoggerInterface $logger
514
     * @return string
515
     */
516
    private function getSelectedSecondFactor(ResponseContext $context, LoggerInterface $logger)
517
    {
518
        $selectedSecondFactor = $context->getSelectedSecondFactor();
519
520
        if (!$selectedSecondFactor) {
521
            $logger->error('Cannot verify possession of an unknown second factor');
522
523
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
524
        }
525
526
        return $selectedSecondFactor;
527
    }
528
}
529