Completed
Pull Request — master (#102)
by Boy
18:16
created

verifySmsSecondFactorChallengeAction()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 52
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 52
rs 8.6868
cc 6
eloc 32
nc 6
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\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->isGssf()) {
115
            $route .= 'gssf';
116
        } else {
117
            $route .= strtolower($secondFactor->secondFactorType);
118
        }
119
120
        return $this->redirect($this->generateUrl($route));
121
    }
122
123
    public function verifyGssfAction()
124
    {
125
        $context = $this->getResponseContext();
126
        $originalRequestId = $context->getInResponseTo();
127
128
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
129
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
130
        $logger->info('Received request to verify GSSF');
131
132
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
133
134
        $logger->info(sprintf(
135
            'Selected GSSF "%s" for verfication, forwarding to Saml handling',
136
            $selectedSecondFactor
137
        ));
138
139
        /** @var \Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService $secondFactorService */
140
        $secondFactorService = $this->get('gateway.service.second_factor_service');
141
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
142
        $secondFactor = $secondFactorService->findByUuid($selectedSecondFactor);
143
        if (!$secondFactor) {
144
            $logger->critical(sprintf(
145
                'Requested verification of GSSF "%s", however that Second Factor no longer exists',
146
                $selectedSecondFactor
147
            ));
148
149
            throw new RuntimeException('Verification of selected second factor that no longer exists');
150
        }
151
152
        return $this->forward(
153
            'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:sendSecondFactorVerificationAuthnRequest',
154
            [
155
                'provider' => $secondFactor->secondFactorType,
156
                'subjectNameId' => $secondFactor->secondFactorIdentifier
157
            ]
158
        );
159
    }
160
161
    public function gssfVerifiedAction()
162
    {
163
        $context = $this->getResponseContext();
164
        $originalRequestId = $context->getInResponseTo();
165
166
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
167
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
168
        $logger->info('Attempting to mark GSSF as verified');
169
170
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
171
172
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
173
        $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor);
174
        if (!$secondFactor) {
175
            $logger->critical(sprintf(
176
                'Verification of GSSF "%s" succeeded, however that Second Factor no longer exists',
177
                $selectedSecondFactor
178
            ));
179
180
            throw new RuntimeException('Verification of selected second factor that no longer exists');
181
        }
182
183
        $context->markSecondFactorVerified();
184
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
185
186
        $logger->info(sprintf(
187
            'Marked GSSF "%s" as verified, forwarding to Gateway controller to respond',
188
            $selectedSecondFactor
189
        ));
190
191
        return $this->forward($context->getResponseAction());
192
    }
193
194
    /**
195
     * @Template
196
     * @param Request $request
197
     * @return array|Response
198
     */
199
    public function verifyYubiKeySecondFactorAction(Request $request)
200
    {
201
        $context = $this->getResponseContext();
202
        $originalRequestId = $context->getInResponseTo();
203
204
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
205
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
206
207
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
208
209
        $logger->notice('Verifying possession of Yubikey second factor');
210
211
        $command = new VerifyYubikeyOtpCommand();
212
        $command->secondFactorId = $selectedSecondFactor;
213
214
        $form = $this->createForm('gateway_verify_yubikey_otp', $command)->handleRequest($request);
215
216
        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...
217
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
218
        }
219
220
        if (!$form->isValid()) {
221
            // OTP field is rendered empty in the template.
222
            return ['form' => $form->createView()];
223
        }
224
225
        $result = $this->getStepupService()->verifyYubikeyOtp($command);
226
227
        if ($result->didOtpVerificationFail()) {
228
            $form->addError(new FormError('gateway.form.verify_yubikey.otp_verification_failed'));
229
230
            // OTP field is rendered empty in the template.
231
            return ['form' => $form->createView()];
232
        } elseif (!$result->didPublicIdMatch()) {
233
            $form->addError(new FormError('gateway.form.verify_yubikey.public_id_mismatch'));
234
235
            // OTP field is rendered empty in the template.
236
            return ['form' => $form->createView()];
237
        }
238
239
        $this->getResponseContext()->markSecondFactorVerified();
240
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
241
242
        $logger->info(
243
            sprintf(
244
                'Marked Yubikey Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
245
                $selectedSecondFactor
246
            )
247
        );
248
249
        return $this->forward($context->getResponseAction());
250
    }
251
252
    /**
253
     * @Template
254
     * @param Request $request
255
     * @return array|Response
256
     */
257
    public function verifySmsSecondFactorAction(Request $request)
258
    {
259
        $context = $this->getResponseContext();
260
        $originalRequestId = $context->getInResponseTo();
261
262
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
263
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
264
265
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
266
267
        $logger->notice('Verifying possession of SMS second factor, preparing to send');
268
269
        $command = new SendSmsChallengeCommand();
270
        $command->secondFactorId = $selectedSecondFactor;
271
272
        $form = $this->createForm('gateway_send_sms_challenge', $command)->handleRequest($request);
273
274
        $stepupService = $this->getStepupService();
275
        $phoneNumber = InternationalPhoneNumber::fromStringFormat(
276
            $stepupService->getSecondFactorIdentifier($selectedSecondFactor)
277
        );
278
279
        $otpRequestsRemaining = $stepupService->getSmsOtpRequestsRemainingCount();
280
        $maximumOtpRequests = $stepupService->getSmsMaximumOtpRequestsCount();
281
        $viewVariables = ['otpRequestsRemaining' => $otpRequestsRemaining, 'maximumOtpRequests' => $maximumOtpRequests];
282
283
        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...
284
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
285
        }
286
287
        if (!$form->isValid()) {
288
            return array_merge($viewVariables, ['phoneNumber' => $phoneNumber, 'form' => $form->createView()]);
289
        }
290
291
        $logger->notice('Verifying possession of SMS second factor, sending challenge per SMS');
292
293
        if (!$stepupService->sendSmsChallenge($command)) {
294
            $form->addError(new FormError('gateway.form.send_sms_challenge.sms_sending_failed'));
295
296
            return array_merge($viewVariables, ['phoneNumber' => $phoneNumber, 'form' => $form->createView()]);
297
        }
298
299
        return $this->redirect($this->generateUrl('gateway_verify_second_factor_sms_verify_challenge'));
300
    }
301
302
    /**
303
     * @Template
304
     * @param Request $request
305
     * @return array|Response
306
     */
307
    public function verifySmsSecondFactorChallengeAction(Request $request)
308
    {
309
        $context = $this->getResponseContext();
310
        $originalRequestId = $context->getInResponseTo();
311
312
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
313
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
314
315
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
316
317
        $command = new VerifyPossessionOfPhoneCommand();
318
        $form = $this->createForm('gateway_verify_sms_challenge', $command)->handleRequest($request);
319
320
        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...
321
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
322
        }
323
324
        if (!$form->isValid()) {
325
            return ['form' => $form->createView()];
326
        }
327
328
        $logger->notice('Verifying input SMS challenge matches');
329
330
        $verification = $this->getStepupService()->verifySmsChallenge($command);
331
332
        if ($verification->wasSuccessful()) {
333
            $this->getStepupService()->clearSmsVerificationState();
334
335
            $this->getResponseContext()->markSecondFactorVerified();
336
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
337
338
            $logger->info(
339
                sprintf(
340
                    'Marked Sms Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
341
                    $selectedSecondFactor
342
                )
343
            );
344
345
            return $this->forward($context->getResponseAction());
346
        } elseif ($verification->didOtpExpire()) {
347
            $logger->notice('SMS challenge expired');
348
            $form->addError(new FormError('gateway.form.send_sms_challenge.challenge_expired'));
349
        } elseif ($verification->wasAttemptedTooManyTimes()) {
350
            $logger->notice('SMS challenge verification was attempted too many times');
351
            $form->addError(new FormError('gateway.form.send_sms_challenge.too_many_attempts'));
352
        } else {
353
            $logger->notice('SMS challenge did not match');
354
            $form->addError(new FormError('gateway.form.send_sms_challenge.sms_challenge_incorrect'));
355
        }
356
357
        return ['form' => $form->createView()];
358
    }
359
360
    /**
361
     * @Template
362
     */
363
    public function initiateU2fAuthenticationAction()
364
    {
365
        $context = $this->getResponseContext();
366
        $originalRequestId = $context->getInResponseTo();
367
368
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
369
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
370
371
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
372
        $stepupService = $this->getStepupService();
373
374
        $cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication');
375
        $cancelForm =
376
            $this->createForm('gateway_cancel_second_factor_verification', null, ['action' => $cancelFormAction]);
377
378
        $logger->notice('Verifying possession of U2F second factor, looking for registration matching key handle');
379
380
        $service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification');
381
        $keyHandle = new KeyHandle($stepupService->getSecondFactorIdentifier($selectedSecondFactor));
382
        $registration = $service->findRegistrationByKeyHandle($keyHandle);
383
384
        if ($registration === null) {
385
            $logger->critical(
386
                sprintf('No known registration for key handle of second factor "%s"', $selectedSecondFactor)
387
            );
388
            $this->addFlash('error', 'gateway.u2f.alert.unknown_registration');
389
390
            return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
391
        }
392
393
        $logger->notice('Creating sign request');
394
395
        $signRequest = $service->createSignRequest($registration);
396
        $signResponse = new SignResponse();
397
398
        /** @var AttributeBagInterface $session */
399
        $session = $this->get('gateway.session.u2f');
400
        $session->set('request', $signRequest);
401
402
        $formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication');
403
        $form = $this->createForm(
404
            'surfnet_stepup_u2f_verify_device_authentication',
405
            $signResponse,
406
            ['sign_request' => $signRequest, 'action' => $formAction]
407
        );
408
409
        return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
410
    }
411
412
    /**
413
     * @Template("SurfnetStepupGatewayGatewayBundle:SecondFactor:initiateU2fAuthentication.html.twig")
414
     *
415
     * @param Request $request
416
     * @return array|Response
417
     */
418
    public function verifyU2fAuthenticationAction(Request $request)
419
    {
420
        $context = $this->getResponseContext();
421
        $originalRequestId = $context->getInResponseTo();
422
423
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
424
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
425
426
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
427
428
        $logger->notice('Received sign response from device');
429
430
        /** @var AttributeBagInterface $session */
431
        $session = $this->get('gateway.session.u2f');
432
        $signRequest = $session->get('request');
433
        $signResponse = new SignResponse();
434
435
        $formAction = $this->generateUrl('gateway_verify_second_factor_u2f_verify_authentication');
436
        $form = $this
437
            ->createForm(
438
                'surfnet_stepup_u2f_verify_device_authentication',
439
                $signResponse,
440
                ['sign_request' => $signRequest, 'action' => $formAction]
441
            )
442
            ->handleRequest($request);
443
444
        $cancelFormAction = $this->generateUrl('gateway_verify_second_factor_u2f_cancel_authentication');
445
        $cancelForm =
446
            $this->createForm('gateway_cancel_second_factor_verification', null, ['action' => $cancelFormAction]);
447
448
        if (!$form->isValid()) {
449
            $logger->error('U2F authentication verification could not be started because device send illegal data');
450
            $this->addFlash('error', 'gateway.u2f.alert.error');
451
452
            return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
453
        }
454
455
        $service = $this->get('surfnet_stepup_u2f_verification.service.u2f_verification');
456
        $result = $service->verifyAuthentication($signRequest, $signResponse);
457
458
        if ($result->wasSuccessful()) {
459
            $context->markSecondFactorVerified();
460
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId);
461
462
            $logger->info(
463
                sprintf(
464
                    'Marked U2F second factor "%s" as verified, forwarding to Saml Proxy to respond',
465
                    $selectedSecondFactor
466
                )
467
            );
468
469
            return $this->forward($context->getResponseAction());
470
        } elseif ($result->didDeviceReportError()) {
471
            $logger->error('U2F device reported error during authentication');
472
            $this->addFlash('error', 'gateway.u2f.alert.device_reported_an_error');
473
        } else {
474
            $logger->error('U2F authentication verification failed');
475
            $this->addFlash('error', 'gateway.u2f.alert.error');
476
        }
477
478
        return ['authenticationFailed' => true, 'cancelForm' => $cancelForm->createView()];
479
    }
480
481
    public function cancelU2fAuthenticationAction()
482
    {
483
        return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
484
    }
485
486
    /**
487
     * @return \Surfnet\StepupGateway\GatewayBundle\Service\StepupAuthenticationService
488
     */
489
    private function getStepupService()
490
    {
491
        return $this->get('gateway.service.stepup_authentication');
492
    }
493
494
    /**
495
     * @return ResponseContext
496
     */
497
    private function getResponseContext()
498
    {
499
        return $this->get($this->get('gateway.proxy.state_handler')->getResponseContextServiceId());
500
    }
501
502
    /**
503
     * @return \Surfnet\StepupGateway\GatewayBundle\Monolog\Logger\AuthenticationLogger
504
     */
505
    private function getAuthenticationLogger()
506
    {
507
        return $this->get('gateway.authentication_logger');
508
    }
509
510
    /**
511
     * @param ResponseContext $context
512
     * @param LoggerInterface $logger
513
     * @return string
514
     */
515
    private function getSelectedSecondFactor(ResponseContext $context, LoggerInterface $logger)
516
    {
517
        $selectedSecondFactor = $context->getSelectedSecondFactor();
518
519
        if (!$selectedSecondFactor) {
520
            $logger->error('Cannot verify possession of an unknown second factor');
521
522
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
523
        }
524
525
        return $selectedSecondFactor;
526
    }
527
}
528