Completed
Push — main ( d02cfb...38ec3a )
by
unknown
22s queued 15s
created

getGsspFallbackService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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