Completed
Pull Request — develop (#90)
by Reinier
21:36 queued 18:40
created

verifyTiqrSecondFactorAction()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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