Completed
Push — test-gssp-jodi ( cac964...ed91e2 )
by
unknown
02:02
created

SecondFactorController::selectAndRedirectTo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 9.4285
cc 2
eloc 11
nc 2
nop 2
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\Exception\RuntimeException;
30
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext;
31
use Surfnet\StepupGateway\U2fVerificationBundle\Value\KeyHandle;
32
use Surfnet\StepupU2fBundle\Dto\SignResponse;
33
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
34
use Symfony\Component\Form\FormError;
35
use Symfony\Component\HttpFoundation\Request;
36
use Symfony\Component\HttpFoundation\Response;
37
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
38
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
39
40
/**
41
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
42
 */
43
class SecondFactorController extends Controller
44
{
45
    public function selectSecondFactorForVerificationAction()
46
    {
47
        $context = $this->getResponseContext();
48
        $originalRequestId = $context->getInResponseTo();
49
50
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
51
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
52
        $logger->notice('Determining which second factor to use...');
53
54
        $requiredLoa = $this
55
            ->getStepupService()
56
            ->resolveHighestRequiredLoa(
57
                $context->getRequiredLoa(),
58
                $context->getIdentityNameId(),
59
                $context->getServiceProvider()
0 ignored issues
show
Bug introduced by
It seems like $context->getServiceProvider() can be null; however, resolveHighestRequiredLoa() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

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