Passed
Pull Request — develop (#295)
by Peter
04:30
created

SecondFactorController::getResponseContext()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 9
rs 10
c 1
b 0
f 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
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
Coding Style introduced by
Missing @link tag in file comment
Loading history...
18
19
namespace Surfnet\StepupGateway\GatewayBundle\Controller;
20
21
use Psr\Log\LoggerInterface;
22
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
23
use Surfnet\StepupBundle\Command\VerifyPossessionOfPhoneCommand;
24
use Surfnet\StepupBundle\Value\PhoneNumber\InternationalPhoneNumber;
25
use Surfnet\StepupBundle\Value\SecondFactorType;
26
use Surfnet\StepupGateway\GatewayBundle\Command\ChooseSecondFactorCommand;
27
use Surfnet\StepupGateway\GatewayBundle\Command\SendSmsChallengeCommand;
28
use Surfnet\StepupGateway\GatewayBundle\Command\VerifyYubikeyOtpCommand;
29
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor;
30
use Surfnet\StepupGateway\GatewayBundle\Exception\InvalidArgumentException;
31
use Surfnet\StepupGateway\GatewayBundle\Exception\LoaCannotBeGivenException;
32
use Surfnet\StepupGateway\GatewayBundle\Exception\RuntimeException;
33
use Surfnet\StepupGateway\GatewayBundle\Form\Type\CancelAuthenticationType;
34
use Surfnet\StepupGateway\GatewayBundle\Form\Type\ChooseSecondFactorType;
35
use Surfnet\StepupGateway\GatewayBundle\Form\Type\SendSmsChallengeType;
36
use Surfnet\StepupGateway\GatewayBundle\Form\Type\VerifySmsChallengeType;
37
use Surfnet\StepupGateway\GatewayBundle\Form\Type\VerifyYubikeyOtpType;
38
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext;
39
use Surfnet\StepupGateway\GatewayBundle\Sso2fa\CookieService;
40
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
41
use Symfony\Component\Form\FormError;
42
use Symfony\Component\Form\FormInterface;
43
use Symfony\Component\HttpFoundation\RedirectResponse;
44
use Symfony\Component\HttpFoundation\Request;
45
use Symfony\Component\HttpFoundation\Response;
46
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
47
48
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
49
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
50
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
51
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
52
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
53
class SecondFactorController extends Controller
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Bundle\Framework...e\Controller\Controller has been deprecated: since Symfony 4.2, use "Symfony\Bundle\FrameworkBundle\Controller\AbstractController" instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

53
class SecondFactorController extends /** @scrutinizer ignore-deprecated */ Controller
Loading history...
54
{
55
    const MODE_SFO = 'sfo';
56
    const MODE_SSO = 'sso';
57
58
    public function selectSecondFactorForVerificationSsoAction(Request $request)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function selectSecondFactorForVerificationSsoAction()
Loading history...
59
    {
60
        return $this->selectSecondFactorForVerificationAction(self::MODE_SSO, $request);
61
    }
62
63
    public function selectSecondFactorForVerificationSfoAction(Request $request)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function selectSecondFactorForVerificationSfoAction()
Loading history...
64
    {
65
        return $this->selectSecondFactorForVerificationAction(self::MODE_SFO, $request);
66
    }
67
68
    public function selectSecondFactorForVerificationAction($authenticationMode, Request $request)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function selectSecondFactorForVerificationAction()
Loading history...
69
    {
70
        $this->supportsAuthenticationMode($authenticationMode);
71
        $context = $this->getResponseContext($authenticationMode);
72
73
        $originalRequestId = $context->getInResponseTo();
74
75
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
76
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
77
        $logger->notice('Determining which second factor to use...');
78
79
        try {
80
            // Retrieve all requirements to determine the required LoA
81
            $requestedLoa = $context->getRequiredLoa();
82
            $spConfiguredLoas = $context->getServiceProvider()->get('configuredLoas');
83
84
            $identityNameId = $context->getIdentityNameId();
85
            $normalizedIdpSho = $context->getNormalizedSchacHomeOrganization();
86
            $normalizedUserSho = $this->getStepupService()->getNormalizedUserShoByIdentityNameId($identityNameId);
87
88
            $requiredLoa = $this
89
                ->getStepupService()
90
                ->resolveHighestRequiredLoa(
91
                    $requestedLoa,
92
                    $spConfiguredLoas,
0 ignored issues
show
Bug introduced by
It seems like $spConfiguredLoas can also be of type null; however, parameter $spConfiguredLoas of Surfnet\StepupGateway\Ga...lveHighestRequiredLoa() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

92
                    /** @scrutinizer ignore-type */ $spConfiguredLoas,
Loading history...
93
                    $normalizedIdpSho,
94
                    $normalizedUserSho
95
                );
96
        } catch (LoaCannotBeGivenException $e) {
97
            // Log the message of the domain exception, this contains a meaningful message.
98
            $logger->notice($e->getMessage());
99
100
            return $this->forward(
101
                'SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven',
102
                ['authenticationMode' => $authenticationMode]
103
            );
104
        }
105
106
        $logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa));
107
108
        if ($this->getStepupService()->isIntrinsicLoa($requiredLoa)) {
109
            $this->get('gateway.authentication_logger')->logIntrinsicLoaAuthentication($originalRequestId);
110
            return $this->forward($context->getResponseAction());
0 ignored issues
show
Bug introduced by
It seems like $context->getResponseAction() can also be of type null; however, parameter $controller of Symfony\Bundle\Framework...r\Controller::forward() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
            return $this->forward(/** @scrutinizer ignore-type */ $context->getResponseAction());
Loading history...
111
        }
112
113
        $secondFactorCollection = $this
114
            ->getStepupService()
115
            ->determineViableSecondFactors(
116
                $context->getIdentityNameId(),
117
                $requiredLoa,
118
                $this->get('gateway.service.whitelist')
119
            );
120
121
        /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
122
         * @var CookieService $ssoCookieService
123
         */
124
        $ssoCookieService = $this->get('gateway.service.sso_2fa_cookie');
125
        if ($ssoCookieService->shouldSkip2faAuthentication(
126
            $context,
127
            $requiredLoa->getLevel(),
128
            $identityNameId,
129
            $secondFactorCollection,
130
            $request
131
        )) {
0 ignored issues
show
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
132
            $logger->notice('Skipping second factor authentication. Required LoA was met by the LoA recorded in the cookie');
133
            $this->getResponseContext($authenticationMode)->markSecondFactorVerified();
134
            $this->getResponseContext($authenticationMode)->markVerifiedBySsoOn2faCookie($ssoCookieService->getCookieFingerprint($request));
135
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId, $authenticationMode);
0 ignored issues
show
Bug introduced by
It seems like $originalRequestId can also be of type null; however, parameter $requestId of Surfnet\StepupGateway\Ga...dFactorAuthentication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

135
            $this->getAuthenticationLogger()->logSecondFactorAuthentication(/** @scrutinizer ignore-type */ $originalRequestId, $authenticationMode);
Loading history...
136
            return $this->forward($context->getResponseAction());
137
        }
138
        switch (count($secondFactorCollection)) {
139
            case 0:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
140
                $logger->notice('No second factors can give the determined Loa');
141
142
                return $this->forward(
143
                    'SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven',
144
                    ['authenticationMode' => $authenticationMode]
145
                );
146
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
147
148
            case 1:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
149
                $secondFactor = $secondFactorCollection->first();
150
                $logger->notice(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
151
                    'Found "%d" second factors, using second factor of type "%s"',
152
                    count($secondFactorCollection),
153
                    $secondFactor->secondFactorType
154
                ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
155
156
                return $this->selectAndRedirectTo($secondFactor, $context, $authenticationMode);
157
                break;
158
159
            default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
160
                return $this->forward(
161
                    'SurfnetStepupGatewayGatewayBundle:SecondFactor:chooseSecondFactor',
162
                    ['authenticationMode' => $authenticationMode, 'secondFactors' => $secondFactorCollection]
163
                );
164
                break;
165
        }
166
    }
167
168
    /**
169
     * The main WAYG screen
170
     * - Shows the token selection screen if you own > 1 token
171
     * - Directly goes to SF auth when identity owns 1 token
172
     *
173
     * @Template
0 ignored issues
show
Coding Style introduced by
Tag @Template cannot be grouped with parameter tags in a doc comment
Loading history...
174
     * @param Request $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 41 spaces but found 1
Loading history...
175
     * @param string $authenticationMode
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 41 spaces but found 1
Loading history...
176
     * @return array|RedirectResponse|Response
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
Tag value for @return tag indented incorrectly; expected 40 spaces but found 1
Loading history...
177
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
0 ignored issues
show
Coding Style introduced by
Tag @SuppressWarnings(PHPMD.ExcessiveMethodLength) cannot be grouped with parameter tags in a doc comment
Loading history...
178
     */
179
    public function chooseSecondFactorAction(Request $request, $authenticationMode)
180
    {
181
        $this->supportsAuthenticationMode($authenticationMode);
182
        $context = $this->getResponseContext($authenticationMode);
183
        $originalRequestId = $context->getInResponseTo();
184
185
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
186
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
187
        $logger->notice('Ask the user which one of his suitable second factor tokens to use...');
188
189
        try {
190
            // Retrieve all requirements to determine the required LoA
191
            $requestedLoa = $context->getRequiredLoa();
192
            $spConfiguredLoas = $context->getServiceProvider()->get('configuredLoas');
193
194
            $normalizedIdpSho = $context->getNormalizedSchacHomeOrganization();
195
            $normalizedUserSho = $this->getStepupService()->getNormalizedUserShoByIdentityNameId($context->getIdentityNameId());
196
197
            $requiredLoa = $this
198
                ->getStepupService()
199
                ->resolveHighestRequiredLoa(
200
                    $requestedLoa,
201
                    $spConfiguredLoas,
0 ignored issues
show
Bug introduced by
It seems like $spConfiguredLoas can also be of type null; however, parameter $spConfiguredLoas of Surfnet\StepupGateway\Ga...lveHighestRequiredLoa() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

201
                    /** @scrutinizer ignore-type */ $spConfiguredLoas,
Loading history...
202
                    $normalizedIdpSho,
203
                    $normalizedUserSho
204
                );
205
        } catch (LoaCannotBeGivenException $e) {
206
            // Log the message of the domain exception, this contains a meaningful message.
207
            $logger->notice($e->getMessage());
208
            return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendLoaCannotBeGiven');
209
        }
210
211
        $logger->notice(sprintf('Determined that the required Loa is "%s"', $requiredLoa));
212
213
        $secondFactors = $this
214
            ->getStepupService()
215
            ->determineViableSecondFactors(
216
                $context->getIdentityNameId(),
217
                $requiredLoa,
218
                $this->get('gateway.service.whitelist')
219
            );
220
221
        $command = new ChooseSecondFactorCommand();
222
        $command->secondFactors = $secondFactors;
0 ignored issues
show
Documentation Bug introduced by
It seems like $secondFactors of type Doctrine\Common\Collections\Collection is incompatible with the declared type Surfnet\StepupGateway\Ga...e\Entity\SecondFactor[] of property $secondFactors.

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

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

Loading history...
223
224
        $form = $this
225
            ->createForm(
226
                ChooseSecondFactorType::class,
227
                $command,
228
                ['action' => $this->generateUrl('gateway_verify_second_factor_choose_second_factor', ['authenticationMode' => $authenticationMode])]
229
            )
230
            ->handleRequest($request);
231
        $cancelForm = $this->buildCancelAuthenticationForm($authenticationMode)->handleRequest($request);
232
233
        if ($form->isSubmitted() && $form->isValid()) {
234
            $buttonName = $form->getClickedButton()->getName();
0 ignored issues
show
Bug introduced by
The method getClickedButton() does not exist on Symfony\Component\Form\FormInterface. It seems like you code against a sub-type of Symfony\Component\Form\FormInterface such as Symfony\Component\Form\Form. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

234
            $buttonName = $form->/** @scrutinizer ignore-call */ getClickedButton()->getName();
Loading history...
235
            $formResults = $request->request->get('gateway_choose_second_factor', false);
236
237
            if (!isset($formResults[$buttonName])) {
238
                throw new InvalidArgumentException(
239
                    sprintf(
240
                        'Second factor type "%s" could not be found in the posted form results.',
241
                        $buttonName
242
                    )
243
                );
244
            }
245
246
            $secondFactorType = $formResults[$buttonName];
247
248
            // Filter the selected second factor from the array collection
249
            $secondFactorFiltered = $secondFactors->filter(
250
                function ($secondFactor) use ($secondFactorType) {
251
                    return $secondFactorType === $secondFactor->secondFactorType;
252
                }
253
            );
254
255
            if ($secondFactorFiltered->isEmpty()) {
256
                throw new InvalidArgumentException(
257
                    sprintf(
258
                        'Second factor type "%s" could not be found in the collection of available second factors.',
259
                        $secondFactorType
260
                    )
261
                );
262
            }
263
264
            $secondFactor = $secondFactorFiltered->first();
265
266
            $logger->notice(sprintf('User chose "%s" to use as second factor', $secondFactorType));
267
268
            // Forward to action to verify possession of second factor
269
            return $this->selectAndRedirectTo($secondFactor, $context, $authenticationMode);
270
        } else if ($form->isSubmitted() && !$form->isValid()) {
271
            $form->addError(
272
                new FormError(
273
                    $this->get('translator')
274
                      ->trans('gateway.form.gateway_choose_second_factor.unknown_second_factor_type')
0 ignored issues
show
Coding Style introduced by
Object operator not indented correctly; expected 24 spaces but found 22
Loading history...
275
                )
276
            );
277
        }
278
279
        return [
280
            'form' => $form->createView(),
281
            'cancelForm' => $cancelForm->createView(),
282
            'secondFactors' => $secondFactors,
283
        ];
284
    }
285
286
    public function verifyGssfAction(Request $request)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function verifyGssfAction()
Loading history...
287
    {
288
        if (!$request->get('authenticationMode', false)) {
289
            throw new RuntimeException('Unable to determine the authentication mode in the GSSP verification action');
290
        }
291
        $authenticationMode = $request->get('authenticationMode');
292
        $this->supportsAuthenticationMode($authenticationMode);
293
        $context = $this->getResponseContext($authenticationMode);
294
295
        $originalRequestId = $context->getInResponseTo();
296
297
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
298
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
299
        $logger->info('Received request to verify GSSF');
300
301
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
302
303
        $logger->info(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
304
            'Selected GSSF "%s" for verfication, forwarding to Saml handling',
305
            $selectedSecondFactor
306
        ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
307
308
        /** @var \Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService $secondFactorService */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
309
        $secondFactorService = $this->get('gateway.service.second_factor_service');
310
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
311
        $secondFactor = $secondFactorService->findByUuid($selectedSecondFactor);
312
        if (!$secondFactor) {
0 ignored issues
show
introduced by
$secondFactor is of type Surfnet\StepupGateway\Ga...dle\Entity\SecondFactor, thus it always evaluated to true.
Loading history...
313
            throw new RuntimeException(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
314
                'Requested verification of GSSF "%s", however that Second Factor no longer exists',
315
                $selectedSecondFactor
316
            ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
317
        }
318
319
        // Also send the response context service id, as later we need to know if this is regular SSO or SFO authn.
320
        $responseContextServiceId = $context->getResponseContextServiceId();
321
322
        return $this->forward(
323
            'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:sendSecondFactorVerificationAuthnRequest',
324
            [
325
                'provider' => $secondFactor->secondFactorType,
326
                'subjectNameId' => $secondFactor->secondFactorIdentifier,
327
                'responseContextServiceId' => $responseContextServiceId,
328
            ]
329
        );
330
    }
331
332
    public function gssfVerifiedAction(Request $request)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function gssfVerifiedAction()
Loading history...
333
    {
334
        $authenticationMode = $request->get('authenticationMode');
335
        $this->supportsAuthenticationMode($authenticationMode);
336
        $context = $this->getResponseContext($authenticationMode);
337
338
        $originalRequestId = $context->getInResponseTo();
339
340
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
341
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
342
        $logger->info('Attempting to mark GSSF as verified');
343
344
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
345
346
        /** @var \Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor $secondFactor */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
347
        $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor);
348
        if (!$secondFactor) {
0 ignored issues
show
introduced by
$secondFactor is of type Surfnet\StepupGateway\Ga...dle\Entity\SecondFactor, thus it always evaluated to true.
Loading history...
349
            throw new RuntimeException(
350
                sprintf(
351
                    'Verification of GSSF "%s" succeeded, however that Second Factor no longer exists',
352
                    $selectedSecondFactor
353
                )
354
            );
355
        }
356
357
        $context->markSecondFactorVerified();
358
        $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId, $authenticationMode);
0 ignored issues
show
Bug introduced by
It seems like $originalRequestId can also be of type null; however, parameter $requestId of Surfnet\StepupGateway\Ga...dFactorAuthentication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

358
        $this->getAuthenticationLogger()->logSecondFactorAuthentication(/** @scrutinizer ignore-type */ $originalRequestId, $authenticationMode);
Loading history...
359
360
        $logger->info(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
361
            'Marked GSSF "%s" as verified, forwarding to Gateway controller to respond',
362
            $selectedSecondFactor
363
        ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
364
        return $this->forward($context->getResponseAction());
0 ignored issues
show
Bug introduced by
It seems like $context->getResponseAction() can also be of type null; however, parameter $controller of Symfony\Bundle\Framework...r\Controller::forward() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

364
        return $this->forward(/** @scrutinizer ignore-type */ $context->getResponseAction());
Loading history...
365
    }
366
367
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
368
     * @Template
0 ignored issues
show
Coding Style introduced by
Tag @Template cannot be grouped with parameter tags in a doc comment
Loading history...
369
     * @param Request $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 4 spaces but found 1
Loading history...
370
     * @return array|Response
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
Tag value for @return tag indented incorrectly; expected 3 spaces but found 1
Loading history...
371
     */
372
    public function verifyYubiKeySecondFactorAction(Request $request)
373
    {
374
        if (!$request->get('authenticationMode', false)) {
375
            throw new RuntimeException('Unable to determine the authentication mode in Yubikey verification action');
376
        }
377
        $authenticationMode = $request->get('authenticationMode');
378
        $this->supportsAuthenticationMode($authenticationMode);
379
        $context = $this->getResponseContext($authenticationMode);
380
        $originalRequestId = $context->getInResponseTo();
381
382
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
383
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
384
385
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
386
387
        $logger->notice('Verifying possession of Yubikey second factor');
388
389
        $command = new VerifyYubikeyOtpCommand();
390
        $command->secondFactorId = $selectedSecondFactor;
391
392
        $form = $this->createForm(VerifyYubikeyOtpType::class, $command)->handleRequest($request);
393
        $cancelForm = $this->buildCancelAuthenticationForm($authenticationMode)->handleRequest($request);
394
395
        if ($form->isSubmitted()  && $form->isValid()) {
396
            $result = $this->getStepupService()->verifyYubikeyOtp($command);
397
            if ($result->didOtpVerificationFail()) {
398
                $form->addError(
399
                    new FormError($this->get('translator')->trans('gateway.form.verify_yubikey.otp_verification_failed'))
400
                );
401
402
                // OTP field is rendered empty in the template.
403
                return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
404
            } elseif (!$result->didPublicIdMatch()) {
405
                $form->addError(
406
                    new FormError($this->get('translator')->trans('gateway.form.verify_yubikey.public_id_mismatch'))
407
                );
408
409
                // OTP field is rendered empty in the template.
410
                return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
411
            }
412
413
            $this->getResponseContext($authenticationMode)->markSecondFactorVerified();
414
            $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId, $authenticationMode);
0 ignored issues
show
Bug introduced by
It seems like $originalRequestId can also be of type null; however, parameter $requestId of Surfnet\StepupGateway\Ga...dFactorAuthentication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

414
            $this->getAuthenticationLogger()->logSecondFactorAuthentication(/** @scrutinizer ignore-type */ $originalRequestId, $authenticationMode);
Loading history...
415
416
            $logger->info(
417
                sprintf(
418
                    'Marked Yubikey Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
419
                    $selectedSecondFactor
420
                )
421
            );
422
            return $this->forward($context->getResponseAction());
0 ignored issues
show
Bug introduced by
It seems like $context->getResponseAction() can also be of type null; however, parameter $controller of Symfony\Bundle\Framework...r\Controller::forward() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

422
            return $this->forward(/** @scrutinizer ignore-type */ $context->getResponseAction());
Loading history...
423
        }
424
425
        // OTP field is rendered empty in the template.
426
        return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
427
    }
428
429
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
430
     * @Template
0 ignored issues
show
Coding Style introduced by
Tag @Template cannot be grouped with parameter tags in a doc comment
Loading history...
431
     * @param Request $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 4 spaces but found 1
Loading history...
432
     * @param string $authenticationMode
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Superfluous parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 4 spaces but found 1
Loading history...
433
     * @return array|Response
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
Tag value for @return tag indented incorrectly; expected 3 spaces but found 1
Loading history...
434
     */
435
    public function verifySmsSecondFactorAction(Request $request)
436
    {
437
        if (!$request->get('authenticationMode', false)) {
438
            throw new RuntimeException('Unable to determine the authentication mode in the SMS verification action');
439
        }
440
        $authenticationMode = $request->get('authenticationMode');
441
        $this->supportsAuthenticationMode($authenticationMode);
442
        $context = $this->getResponseContext($authenticationMode);
443
        $originalRequestId = $context->getInResponseTo();
444
445
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
446
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
447
448
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
449
450
        $logger->notice('Verifying possession of SMS second factor, preparing to send');
451
452
        $command = new SendSmsChallengeCommand();
453
        $command->secondFactorId = $selectedSecondFactor;
454
455
        $form = $this->createForm(SendSmsChallengeType::class, $command)->handleRequest($request);
456
        $cancelForm = $this->buildCancelAuthenticationForm($authenticationMode)->handleRequest($request);
457
458
        $stepupService = $this->getStepupService();
459
        $phoneNumber = InternationalPhoneNumber::fromStringFormat(
460
            $stepupService->getSecondFactorIdentifier($selectedSecondFactor)
461
        );
462
463
        $otpRequestsRemaining = $stepupService->getSmsOtpRequestsRemainingCount($selectedSecondFactor);
464
        $maximumOtpRequests = $stepupService->getSmsMaximumOtpRequestsCount();
465
        $viewVariables = ['otpRequestsRemaining' => $otpRequestsRemaining, 'maximumOtpRequests' => $maximumOtpRequests];
466
467
        if ($form->isSubmitted() && !$form->isValid()) {
468
            return array_merge(
469
                $viewVariables,
470
                [
471
                    'phoneNumber' => $phoneNumber,
472
                    'form' => $form->createView(),
473
                    'cancelForm' => $cancelForm->createView()
474
                ]
475
            );
476
        }
477
478
        $logger->notice('Verifying possession of SMS second factor, sending challenge per SMS');
479
480
        if (!$stepupService->sendSmsChallenge($command)) {
481
            $form->addError(
482
                new FormError($this->get('translator')->trans('gateway.form.send_sms_challenge.sms_sending_failed'))
483
            );
484
485
            return array_merge(
486
                $viewVariables,
487
                [
488
                    'phoneNumber' => $phoneNumber,
489
                    'form' => $form->createView(),
490
                    'cancelForm' => $cancelForm->createView()
491
                ]
492
            );
493
        }
494
        return $this->redirect(
495
            $this->generateUrl(
496
                'gateway_verify_second_factor_sms_verify_challenge',
497
                ['authenticationMode' => $authenticationMode]
498
            )
499
        );
500
    }
501
502
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
503
     * @Template
0 ignored issues
show
Coding Style introduced by
Tag @Template cannot be grouped with parameter tags in a doc comment
Loading history...
504
     * @param Request $request
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 4 spaces but found 1
Loading history...
505
     * @param string $authenticationMode
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Superfluous parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 4 spaces but found 1
Loading history...
506
     * @return array|Response
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
Coding Style introduced by
Tag value for @return tag indented incorrectly; expected 3 spaces but found 1
Loading history...
507
     */
508
    public function verifySmsSecondFactorChallengeAction(Request $request)
509
    {
510
        if (!$request->get('authenticationMode', false)) {
511
            throw new RuntimeException('Unable to determine the authentication mode in the SMS challenge action');
512
        }
513
        $authenticationMode = $request->get('authenticationMode');
514
        $this->supportsAuthenticationMode($authenticationMode);
515
        $context = $this->getResponseContext($authenticationMode);
516
        $originalRequestId = $context->getInResponseTo();
517
518
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
519
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
520
521
        $selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
522
523
        $command = new VerifyPossessionOfPhoneCommand();
524
        $form = $this->createForm(VerifySmsChallengeType::class, $command)->handleRequest($request);
525
        $cancelForm = $this->buildCancelAuthenticationForm($authenticationMode)->handleRequest($request);
526
527
        if ($form->isSubmitted() && $form->isValid()) {
528
            $logger->notice('Verifying input SMS challenge matches');
529
            $command->secondFactorId = $selectedSecondFactor;
530
            $verification = $this->getStepupService()->verifySmsChallenge($command);
531
532
            if ($verification->wasSuccessful()) {
533
                $this->getStepupService()->clearSmsVerificationState($selectedSecondFactor);
534
535
                $this->getResponseContext($authenticationMode)->markSecondFactorVerified();
536
                $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId, $authenticationMode);
0 ignored issues
show
Bug introduced by
It seems like $originalRequestId can also be of type null; however, parameter $requestId of Surfnet\StepupGateway\Ga...dFactorAuthentication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

536
                $this->getAuthenticationLogger()->logSecondFactorAuthentication(/** @scrutinizer ignore-type */ $originalRequestId, $authenticationMode);
Loading history...
537
538
                $logger->info(
539
                    sprintf(
540
                        'Marked Sms Second Factor "%s" as verified, forwarding to Saml Proxy to respond',
541
                        $selectedSecondFactor
542
                    )
543
                );
544
                return $this->forward($context->getResponseAction());
0 ignored issues
show
Bug introduced by
It seems like $context->getResponseAction() can also be of type null; however, parameter $controller of Symfony\Bundle\Framework...r\Controller::forward() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

544
                return $this->forward(/** @scrutinizer ignore-type */ $context->getResponseAction());
Loading history...
545
            } elseif ($verification->didOtpExpire()) {
546
                $logger->notice('SMS challenge expired');
547
                $form->addError(
548
                    new FormError($this->get('translator')->trans('gateway.form.send_sms_challenge.challenge_expired'))
549
                );
550
            } elseif ($verification->wasAttemptedTooManyTimes()) {
551
                $logger->notice('SMS challenge verification was attempted too many times');
552
                $form->addError(
553
                    new FormError($this->get('translator')->trans('gateway.form.send_sms_challenge.too_many_attempts'))
554
                );
555
            } else {
556
                $logger->notice('SMS challenge did not match');
557
                $form->addError(
558
                    new FormError(
559
                        $this->get('translator')->trans('gateway.form.send_sms_challenge.sms_challenge_incorrect')
560
                    )
561
                );
562
            }
563
        }
564
        return ['form' => $form->createView(), 'cancelForm' => $cancelForm->createView()];
565
    }
566
567
    public function cancelAuthenticationAction()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function cancelAuthenticationAction()
Loading history...
568
    {
569
        return $this->forward('SurfnetStepupGatewayGatewayBundle:Gateway:sendAuthenticationCancelledByUser');
570
    }
571
572
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
573
     * @return \Surfnet\StepupGateway\GatewayBundle\Service\StepupAuthenticationService
574
     */
575
    private function getStepupService()
0 ignored issues
show
Coding Style introduced by
Private method name "SecondFactorController::getStepupService" must be prefixed with an underscore
Loading history...
576
    {
577
        return $this->get('gateway.service.stepup_authentication');
578
    }
579
580
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $authenticationMode should have a doc-comment as per coding-style.
Loading history...
581
     * @return ResponseContext
582
     */
583
    private function getResponseContext($authenticationMode)
0 ignored issues
show
Coding Style introduced by
Private method name "SecondFactorController::getResponseContext" must be prefixed with an underscore
Loading history...
584
    {
585
        switch ($authenticationMode) {
586
            case self::MODE_SFO:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
587
                return $this->get($this->get('gateway.proxy.sfo.state_handler')->getResponseContextServiceId());
588
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
589
            case self::MODE_SSO:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
590
                return $this->get($this->get('gateway.proxy.sso.state_handler')->getResponseContextServiceId());
591
                break;
592
        }
593
    }
594
595
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
596
     * @return \Surfnet\StepupGateway\GatewayBundle\Monolog\Logger\AuthenticationLogger
597
     */
598
    private function getAuthenticationLogger()
0 ignored issues
show
Coding Style introduced by
Private method name "SecondFactorController::getAuthenticationLogger" must be prefixed with an underscore
Loading history...
599
    {
600
        return $this->get('gateway.authentication_logger');
601
    }
602
603
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
604
     * @param ResponseContext $context
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
605
     * @param LoggerInterface $logger
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
606
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
607
     */
608
    private function getSelectedSecondFactor(ResponseContext $context, LoggerInterface $logger)
0 ignored issues
show
Coding Style introduced by
Private method name "SecondFactorController::getSelectedSecondFactor" must be prefixed with an underscore
Loading history...
609
    {
610
        $selectedSecondFactor = $context->getSelectedSecondFactor();
611
612
        if (!$selectedSecondFactor) {
613
            $logger->error('Cannot verify possession of an unknown second factor');
614
615
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
616
        }
617
618
        return $selectedSecondFactor;
619
    }
620
621
    private function selectAndRedirectTo(SecondFactor $secondFactor, ResponseContext $context, $authenticationMode)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function selectAndRedirectTo()
Loading history...
Coding Style introduced by
Private method name "SecondFactorController::selectAndRedirectTo" must be prefixed with an underscore
Loading history...
622
    {
623
        $context->saveSelectedSecondFactor($secondFactor);
624
625
        $this->getStepupService()->clearSmsVerificationState($secondFactor->secondFactorId);
626
627
        $secondFactorTypeService = $this->get('surfnet_stepup.service.second_factor_type');
628
        $secondFactorType = new SecondFactorType($secondFactor->secondFactorType);
629
630
        $route = 'gateway_verify_second_factor_';
631
        if ($secondFactorTypeService->isGssf($secondFactorType)) {
632
            $route .= 'gssf';
633
        } else {
634
            $route .= strtolower($secondFactor->secondFactorType);
635
        }
636
637
        return $this->redirect($this->generateUrl($route, ['authenticationMode' => $authenticationMode]));
638
    }
639
640
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
641
     * @param string $authenticationMode
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
642
     * @return FormInterface
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
643
     */
644
    private function buildCancelAuthenticationForm($authenticationMode)
0 ignored issues
show
Coding Style introduced by
Private method name "SecondFactorController::buildCancelAuthenticationForm" must be prefixed with an underscore
Loading history...
645
    {
646
        $cancelFormAction = $this->generateUrl(
647
            'gateway_cancel_authentication',
648
            ['authenticationMode' => $authenticationMode]
649
        );
650
651
        return $this->createForm(
652
            CancelAuthenticationType::class,
653
            null,
654
            ['action' => $cancelFormAction]
655
        );
656
    }
657
658
    private function supportsAuthenticationMode($authenticationMode)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function supportsAuthenticationMode()
Loading history...
Coding Style introduced by
Private method name "SecondFactorController::supportsAuthenticationMode" must be prefixed with an underscore
Loading history...
659
    {
660
        if (!($authenticationMode === self::MODE_SSO || $authenticationMode === self::MODE_SFO)) {
661
            throw new InvalidArgumentException('Invalid authentication mode requested');
662
        }
663
    }
664
}
665