Passed
Push — develop ( d2d0cb...6c46b1 )
by Pieter van der
02:42
created

SecondFactorOnlyController::respondAction()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 54
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 29
nc 6
nop 1
dl 0
loc 54
rs 9.1448
c 1
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Copyright 2016 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\SecondFactorOnlyBundle\Controller;
20
21
use Surfnet\StepupGateway\GatewayBundle\Container\ContainerController;
22
use Surfnet\StepupGateway\GatewayBundle\Exception\RequesterFailureException;
23
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\Exception\InvalidAdfsRequestException;
24
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\Exception\InvalidAdfsResponseException;
25
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Exception\InvalidSecondFactorMethodException;
26
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\Gateway\AdfsService;
27
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\Gateway\LoginService;
28
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\Gateway\RespondService;
29
use Symfony\Component\HttpFoundation\Request;
30
use Symfony\Component\HttpFoundation\Response;
31
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
32
use Symfony\Component\Routing\Attribute\Route;
33
34
/**
35
 * Entry point for the Stepup SFO flow.
36
 *
37
 * See docs/GatewayState.md for a high-level diagram on how this controller
38
 * interacts with outside actors and other parts of Stepup.
39
 */
40
class SecondFactorOnlyController extends ContainerController
41
{
42
    /**
43
     * Receive an AuthnRequest from a service provider.
44
     *
45
     * This action will forward the user using an internal redirect to the
46
     * SecondFactorController to start the actual second factor verification.
47
     *
48
     * This action also detects if the request is made by ADFS, and tracks
49
     * some additional information in the session of the user in order to send
50
     * a non-standard response back to ADFS.
51
     *
52
     * @param Request $httpRequest
53
     * @return Response
54
     * @throws InvalidAdfsRequestException
55
     */
56
    #[Route(
57
        path: '/second-factor-only/single-sign-on',
58
        name: 'gateway_second_factor_only_identityprovider_sso',
59
        methods: ['GET', 'POST']
60
    )]
61
    public function sso(Request $httpRequest): Response
62
    {
63
        $logger = $this->get('logger');
64
65
        if (!$this->getParameter('second_factor_only')) {
66
            $logger->notice('Access to ssoAction denied, second_factor_only parameter set to false.');
67
68
            throw $this->createAccessDeniedException('Second Factor Only feature is disabled');
69
        }
70
71
        $logger->notice('Received AuthnRequest on second-factor-only endpoint, started processing');
72
73
        $secondFactorLoginService = $this->getSecondFactorLoginService();
74
75
        // Handle binding
76
        $originalRequest = $secondFactorLoginService->handleBinding($httpRequest);
77
78
        // Transform ADFS request to Authn request if applicable
79
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequest->getRequestId());
80
        $httpRequest = $this->getSecondFactorAdfsService()->handleAdfsRequest($logger, $httpRequest, $originalRequest);
81
82
        try {
83
            $secondFactorLoginService->singleSignOn($httpRequest, $originalRequest);
84
        } catch (RequesterFailureException $e) {
85
            /** @var \Surfnet\StepupGateway\GatewayBundle\Service\ResponseRenderingService $responseRendering */
86
            $responseRendering = $this->get('second_factor_only.response_rendering');
87
88
            return $responseRendering->renderRequesterFailureResponse($this->getResponseContext(), $httpRequest);
89
        }
90
91
        $logger->notice('Forwarding to second factor controller for loa determination and handling');
92
93
        // Forward to the selectSecondFactorForVerificationSsoAction,
94
        // this in turn will forward to the correct
95
        // verification action (based on authentication type sso/sfo)
96
        return $this->forward('Surfnet\StepupGateway\GatewayBundle\Controller\SecondFactorController::selectSecondFactorForVerificationSfo');
97
    }
98
99
    /**
100
     * Send a SAML response back to the service provider.
101
     *
102
     * Second factor verification handled by SecondFactorController is
103
     * finished. The user was forwarded back to this action with an internal
104
     * redirect. This method sends a AuthnResponse back to the service
105
     * provider in response to the AuthnRequest received in ssoAction().
106
     *
107
     * When responding to an ADFS authentication, the additional ADFS
108
     * parameters (Context, AuthMethod) are added to the POST response data.
109
     * In this case, the SAMLResponse parameter is prepended with an
110
     * underscore. And finally the ACS location the SAMLResponse wil be sent
111
     * to, is updated to use the ACS location set in the original AuthNRequest.
112
     *
113
     * @return Response
114
     * @throws InvalidAdfsResponseException
115
     */
116
    public function respond(Request $request): Response
117
    {
118
        $responseContext = $this->getResponseContext();
119
        $originalRequestId = $responseContext->getInResponseTo();
120
121
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
122
123
        $responseRendering = $this->get('second_factor_only.response_rendering');
124
125
        if (!$this->getParameter('second_factor_only')) {
126
            $logger->notice(sprintf(
127
                'Access to %s denied, second_factor_only parameter set to false.',
128
                __METHOD__
129
            ));
130
            throw $this->createAccessDeniedException('Second Factor Only feature disabled');
131
        }
132
133
        try {
134
            $response = $this->getSecondFactorRespondService()->respond($responseContext, $request);
135
        } catch (InvalidSecondFactorMethodException $e) {
136
            throw new BadRequestHttpException($e->getMessage());
137
        }
138
139
        // Reset state
140
        $this->getSecondFactorRespondService()->resetRespondState($responseContext);
141
142
        // Check if ADFS response, if it is, we use the ADFS ACS twig template
143
        $adfsParameters = $this->getSecondFactorAdfsService()->handleAdfsResponse($logger, $responseContext);
144
        if (!is_null($adfsParameters)) {
145
            // Handle Adfs response
146
            $xmlResponse = $responseRendering->getResponseAsXML($response);
147
148
            $httpResponse = $this->render(
149
                '@SurfnetStepupGatewaySecondFactorOnly/adfs/consume_assertion.html.twig',
150
                [
151
                    'acu' => $responseContext->getDestinationForAdfs(),
152
                    'samlResponse' => $xmlResponse,
153
                    'adfs' => $adfsParameters,
154
                ]
155
            );
156
        } else {
157
            // Render the regular SAML response, we do not return it yet, the SSO on 2FA handler will use it to store
158
            // the SSO on 2FA cookie.
159
            $httpResponse =  $responseRendering->renderResponse($responseContext, $response, $request);
160
        }
161
162
        if ($response->isSuccess()) {
163
            $ssoCookieService = $this->get('gateway.service.sso_2fa_cookie');
164
            $ssoCookieService->handleSsoOn2faCookieStorage($responseContext, $request, $httpResponse);
165
        }
166
        // We can now forget the selected second factor.
167
        $responseContext->finalizeAuthentication();
168
169
        return $httpResponse;
170
    }
171
172
    /**
173
     * @return \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext
174
     */
175
    public function getResponseContext()
176
    {
177
        return $this->get('second_factor_only.response_context');
178
    }
179
180
    /**
181
     * @return LoginService
182
     */
183
    public function getSecondFactorLoginService()
184
    {
185
        return $this->get('second_factor_only.login_service');
186
    }
187
188
    /**
189
     * @return RespondService
190
     */
191
    public function getSecondFactorRespondService()
192
    {
193
        return $this->get('second_factor_only.respond_service');
194
    }
195
196
    /**
197
     * @return AdfsService
198
     */
199
    public function getSecondFactorAdfsService()
200
    {
201
        return $this->get('second_factor_only.adfs_service');
202
    }
203
}
204