Completed
Push — master ( e96383...13486f )
by
unknown
06:11
created

SecondFactorOnlyController   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 13
lcom 1
cbo 10
dl 0
loc 212
rs 10
c 5
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
B respondAction() 0 82 6
B ssoAction() 0 95 6
A getResponseContext() 0 4 1
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\SecondFactorOnlyBundle\Controller;
20
21
use Exception;
22
use Surfnet\SamlBundle\SAML2\AuthnRequest;
23
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\Exception\InvalidAdfsRequestException;
24
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\Exception\InvalidAdfsResponseException;
25
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Saml\ResponseFactory;
26
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\LoaAliasLookupService;
27
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
28
use Symfony\Component\HttpFoundation\Request;
29
use Symfony\Component\HttpFoundation\Response;
30
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
31
32
/**
33
 * Entry point for the Stepup SFO flow.
34
 *
35
 * See docs/GatewayState.md for a high-level diagram on how this controller
36
 * interacts with outside actors and other parts of Stepup.
37
 */
38
class SecondFactorOnlyController extends Controller
39
{
40
    /**
41
     * Receive an AuthnRequest from a service provider.
42
     *
43
     * This action will forward the user using an internal redirect to the
44
     * SecondFactorController to start the actual second factor verification.
45
     *
46
     * This action also detects if the request is made by ADFS, and tracks
47
     * some additional information in the session of the user in order to send
48
     * a non-standard response back to ADFS.
49
     *
50
     * @param Request $httpRequest
51
     * @return Response
52
     */
53
    public function ssoAction(Request $httpRequest)
54
    {
55
        $logger = $this->get('logger');
56
57
        if (!$this->getParameter('second_factor_only')) {
58
            $logger->notice('Access to ssoAction denied, second_factor_only parameter set to false.');
59
60
            throw $this->createAccessDeniedException('Second Factor Only feature is disabled');
61
        }
62
63
        $logger->notice('Received AuthnRequest on second-factor-only endpoint, started processing');
64
65
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
66
        $bindingFactory = $this->get('second_factor_only.http.binding_factory');
67
68
        $logger->notice('Determine what type of Binding is used in the Request');
69
        $binding = $bindingFactory->build($httpRequest);
70
71
        /** @var \Surfnet\SamlBundle\SAML2\ReceivedAuthnRequest $originalRequest */
72
        $originalRequest = $binding->receiveSignedAuthnRequestFrom($httpRequest);
73
74
        $originalRequestId = $originalRequest->getRequestId();
75
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
76
        $logger->notice(sprintf(
77
            'AuthnRequest processing complete, received AuthnRequest from "%s", request ID: "%s"',
78
            $originalRequest->getServiceProvider(),
79
            $originalRequest->getRequestId()
80
        ));
81
82
        // ADFS support
83
        $adfsHelper = $this->get('second_factor_only.adfs.request_helper');
84
        if ($adfsHelper->isAdfsRequest($httpRequest)) {
85
            $logger->notice('Received AuthnRequest from an ADFS');
86
            try {
87
                $httpRequest = $adfsHelper->transformRequest(
88
                    $httpRequest,
89
                    $originalRequest->getRequestId()
90
                );
91
            } catch (Exception $e) {
92
                throw new InvalidAdfsRequestException(
93
                    sprintf('Could not process ADFS Request, error: "%s"', $e->getMessage())
94
                );
95
            }
96
        }
97
98
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler $stateHandler */
99
        $stateHandler = $this->get('gateway.proxy.state_handler');
100
101
        // Clear the state of the previous SSO action. Request data of previous
102
        // SSO actions should not have any effect in subsequent SSO actions.
103
        $stateHandler->clear();
104
105
        $stateHandler
106
            ->setRequestId($originalRequestId)
107
            ->setRequestServiceProvider($originalRequest->getServiceProvider())
0 ignored issues
show
Bug introduced by
It seems like $originalRequest->getServiceProvider() targeting Surfnet\SamlBundle\SAML2...t::getServiceProvider() can also be of type null or object<SAML2\XML\saml\Issuer>; however, Surfnet\StepupGateway\Ga...equestServiceProvider() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
108
            ->setRequestAssertionConsumerServiceUrl($originalRequest->getAssertionConsumerServiceURL())
109
            ->setRelayState($httpRequest->get(AuthnRequest::PARAMETER_RELAY_STATE, ''))
110
            ->setResponseAction('SurfnetStepupGatewaySecondFactorOnlyBundle:SecondFactorOnly:respond')
111
            ->setResponseContextServiceId('second_factor_only.response_context');
112
113
        // Check if the NameID is provided and we may use it.
114
        $nameId = $originalRequest->getNameId();
115
        $secondFactorOnlyNameIdValidator = $this->get('second_factor_only.validate_nameid')->with($logger);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $secondFactorOnlyNameIdValidator exceeds the maximum configured length of 30.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
116
        $serviceProviderMayUseSecondFactorOnly = $secondFactorOnlyNameIdValidator->validate(
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $serviceProviderMayUseSecondFactorOnly exceeds the maximum configured length of 30.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
117
            $originalRequest->getServiceProvider(),
118
            $nameId
119
        );
120
121
        if (!$serviceProviderMayUseSecondFactorOnly) {
122
            /** @var \Surfnet\StepupGateway\GatewayBundle\Service\ResponseRenderingService $responseRendering */
123
            $responseRendering = $this->get('second_factor_only.response_rendering');
124
125
            return $responseRendering->renderRequesterFailureResponse($this->getResponseContext());
126
        }
127
128
        $stateHandler->saveIdentityNameId($nameId);
129
130
        // Check if the requested Loa is provided and supported.
131
        $loaId = $this->get('second_factor_only.loa_resolution')->with($logger)->resolve(
132
            $originalRequest->getAuthenticationContextClassRef()
133
        );
134
135
        if (empty($loaId)) {
136
            /** @var \Surfnet\StepupGateway\GatewayBundle\Service\ResponseRenderingService $responseRendering */
137
            $responseRendering = $this->get('second_factor_only.response_rendering');
138
139
            return $responseRendering->renderRequesterFailureResponse($this->getResponseContext());
140
        }
141
142
        $stateHandler->setRequiredLoaIdentifier($loaId);
143
144
        $logger->notice('Forwarding to second factor controller for loa determination and handling');
145
146
        return $this->forward('SurfnetStepupGatewayGatewayBundle:SecondFactor:selectSecondFactorForVerification');
147
    }
148
149
    /**
150
     * Send a SAML response back to the service provider.
151
     *
152
     * Second factor verification handled by SecondFactorController is
153
     * finished. The user was forwarded back to this action with an internal
154
     * redirect. This method sends a AuthnResponse back to the service
155
     * provider in response to the AuthnRequest received in ssoAction().
156
     *
157
     * @return Response
158
     */
159
    public function respondAction()
160
    {
161
        $responseContext = $this->getResponseContext();
162
        $originalRequestId = $responseContext->getInResponseTo();
163
164
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
165
166
        if (!$this->getParameter('second_factor_only')) {
167
            $logger->notice(sprintf(
168
                'Access to %s denied, second_factor_only parameter set to false.',
169
                __METHOD__
170
            ));
171
            throw $this->createAccessDeniedException('Second Factor Only feature disabled');
172
        }
173
174
        $logger->notice('Creating second-factor-only Response');
175
176
        $selectedSecondFactorUuid = $this->getResponseContext()->getSelectedSecondFactor();
177
        if (!$selectedSecondFactorUuid) {
178
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
179
        }
180
181
        if (!$responseContext->isSecondFactorVerified()) {
182
            throw new BadRequestHttpException(
183
                'Second factor was not verified'
184
            );
185
        }
186
187
        $secondFactor = $this->get('gateway.service.second_factor_service')
188
            ->findByUuid($selectedSecondFactorUuid);
189
        $secondFactorTypeService = $this->get('surfnet_stepup.service.second_factor_type');
190
        $grantedLoa = $this->get('surfnet_stepup.service.loa_resolution')
191
            ->getLoaByLevel($secondFactor->getLoaLevel($secondFactorTypeService));
192
193
        /** @var LoaAliasLookupService $loaAliasLookup */
194
        $loaAliasLookup = $this->get('second_factor_only.loa_alias_lookup');
195
        $authnContextClassRef = $loaAliasLookup->findAliasByLoa($grantedLoa);
196
197
        /** @var ResponseFactory $response_factory */
198
        $responseFactory = $this->get('second_factor_only.saml_response_factory');
199
200
        $response = $responseFactory->createSecondFactorOnlyResponse(
201
            $responseContext->getIdentityNameId(),
202
            $responseContext->getDestination(),
203
            $authnContextClassRef
204
        );
205
206
        $responseContext->responseSent();
207
208
        $logger->notice(sprintf(
209
            'Responding to request "%s" with newly created response "%s"',
210
            $responseContext->getInResponseTo(),
211
            $response->getId()
212
        ));
213
214
        $responseRendering = $this->get('second_factor_only.response_rendering');
215
216
        $adfsHelper = $this->get('second_factor_only.adfs.response_helper');
217
        if ($adfsHelper->isAdfsResponse($originalRequestId)) {
218
            $xmlResponse = $responseRendering->getResponseAsXML($response);
219
            try {
220
                $adfsParameters = $adfsHelper->retrieveAdfsParameters();
221
            } catch (Exception $e) {
222
                throw new InvalidAdfsResponseException(
223
                    sprintf('Could not process ADFS Response parameters, error: "%s"', $e->getMessage())
224
                );
225
            }
226
227
            $logger->notice('Sending ACS Response to ADFS plugin');
228
229
            return $this->render(
230
                '@SurfnetStepupGatewaySecondFactorOnly/Adfs/consumeAssertion.html.twig',
231
                [
232
                    'acu' => $responseContext->getDestinationForAdfs(),
233
                    'samlResponse' => $xmlResponse,
234
                    'context' => $adfsParameters->getContext(),
235
                    'authMethod' => $adfsParameters->getAuthMethod(),
236
                ]
237
            );
238
        }
239
        return $responseRendering->renderResponse($responseContext, $response);
240
    }
241
242
    /**
243
     * @return \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext
244
     */
245
    public function getResponseContext()
246
    {
247
        return $this->get('second_factor_only.response_context');
248
    }
249
}
250