Completed
Pull Request — develop (#166)
by
unknown
02:48
created

GatewayController::consumeAssertionAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 2
nc 2
nop 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\GatewayBundle\Controller;
20
21
use SAML2\Constants;
22
use SAML2\Response as SAMLResponse;
23
use Surfnet\StepupGateway\GatewayBundle\Exception\RequesterFailureException;
24
use Surfnet\StepupGateway\GatewayBundle\Exception\ResponseFailureException;
25
use Surfnet\StepupGateway\GatewayBundle\Service\Gateway\ConsumeAssertionService;
26
use Surfnet\StepupGateway\GatewayBundle\Service\Gateway\FailedResponseService;
27
use Surfnet\StepupGateway\GatewayBundle\Service\Gateway\LoginService;
28
use Surfnet\StepupGateway\GatewayBundle\Service\Gateway\RespondService;
29
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
30
use Symfony\Component\HttpFoundation\Request;
31
use Symfony\Component\HttpFoundation\Response;
32
use Symfony\Component\HttpKernel\Exception\HttpException;
33
34
/**
35
 * Entry point for the Stepup login 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 GatewayController extends Controller
41
{
42
    const RESPONSE_CONTEXT_SERVICE_ID = 'gateway.proxy.response_context';
43
44
    /**
45
     * Receive an AuthnRequest from a service provider.
46
     *
47
     * The service provider is either a Stepup component (SelfService, RA) or
48
     * an external service provider.
49
     *
50
     * This single sign-on action will start a new SAML request to the remote
51
     * IDP configured in Stepup (most likely to be an instance of OpenConext
52
     * EngineBlock).
53
     *
54
     * @param Request $httpRequest
55
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
56
     */
57
    public function ssoAction(Request $httpRequest)
58
    {
59
        /** @var \Psr\Log\LoggerInterface $logger */
60
        $logger = $this->get('logger');
61
62
        $redirectBinding = $this->get('surfnet_saml.http.redirect_binding');
63
        $gatewayLoginService = $this->getGatewayLoginService();
64
65
        $logger->notice('Received AuthnRequest, started processing');
66
67
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
68
        $originalRequest = $redirectBinding->receiveSignedAuthnRequestFrom($httpRequest);
69
70
        try {
71
            $proxyRequest = $gatewayLoginService->singleSignOn($httpRequest, $originalRequest);
72
        } catch (RequesterFailureException $e) {
73
            $response = $this->createRequesterFailureResponse();
74
75
            return $this->renderSamlResponse('consumeAssertion', $response);
76
        }
77
78
        return $redirectBinding->createResponseFor($proxyRequest);
79
    }
80
81
    /**
82
     *
83
     */
84
    public function proxySsoAction()
85
    {
86
        throw new HttpException(418, 'Not Yet Implemented');
87
    }
88
89
    /**
90
     * Receive an AuthnResponse from an identity provider.
91
     *
92
     * The AuthnRequest started in ssoAction() resulted in an AuthnResponse
93
     * from the IDP. This method handles the assertion and forwards the user
94
     * using an internal redirect to the SecondFactorController to start the
95
     * actual second factor verification.
96
     *
97
     * @param Request $request
98
     * @return \Symfony\Component\HttpFoundation\Response
99
     */
100
    public function consumeAssertionAction(Request $request)
101
    {
102
        $responseContext = $this->getResponseContext();
103
        $gatewayLoginService = $this->getGatewayConsumeAssertionService();
104
105
        try {
106
            $gatewayLoginService->consumeAssertion($request, $responseContext);
107
        } catch (ResponseFailureException $e) {
108
            $response = $this->createResponseFailureResponse($responseContext);
109
110
            return $this->renderSamlResponse('unprocessableResponse', $response);
111
        }
112
113
        return $this->forward('SurfnetStepupGatewayGatewayBundle:SecondFactor:selectSecondFactorForVerification');
114
    }
115
116
    /**
117
     * Send a SAML response back to the service provider.
118
     *
119
     * Second factor verification handled by SecondFactorController is
120
     * finished. The user was forwarded back to this action with an internal
121
     * redirect. This method sends a AuthnResponse back to the service
122
     * provider in response to the AuthnRequest received in ssoAction().
123
     */
124
    public function respondAction()
125
    {
126
        $responseContext = $this->getResponseContext();
127
        $gatewayLoginService = $this->getGatewayRespondService();
128
129
        $response = $gatewayLoginService->respond($responseContext);
130
131
        return $this->renderSamlResponse('consumeAssertion', $response);
132
    }
133
134
    /**
135
     * @return Response
136
     */
137 View Code Duplication
    public function sendLoaCannotBeGivenAction()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
    {
139
        $responseContext = $this->getResponseContext();
140
        $gatewayLoginService = $this->getGatewayFailedResponseService();
141
142
        $response = $gatewayLoginService->sendLoaCannotBeGiven($responseContext);
143
144
        return $this->renderSamlResponse('consumeAssertion', $response);
145
    }
146
147
    /**
148
     * @return Response
149
     */
150 View Code Duplication
    public function sendAuthenticationCancelledByUserAction()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
    {
152
        $responseContext = $this->getResponseContext();
153
        $gatewayLoginService = $this->getGatewayFailedResponseService();
154
155
        $response = $gatewayLoginService->sendAuthenticationCancelledByUser($responseContext);
156
157
        return $this->renderSamlResponse('consumeAssertion', $response);
158
    }
159
160
    /**
161
     * @param string         $view
162
     * @param SAMLResponse $response
163
     * @return Response
164
     */
165 View Code Duplication
    public function renderSamlResponse($view, SAMLResponse $response)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167
        $responseContext = $this->getResponseContext();
168
169
        return $this->render($view, [
170
            'acu'        => $responseContext->getDestination(),
171
            'response'   => $this->getResponseAsXML($response),
172
            'relayState' => $responseContext->getRelayState()
173
        ]);
174
    }
175
176
    /**
177
     * @param string   $view
178
     * @param array    $parameters
179
     * @param Response $response
0 ignored issues
show
Documentation introduced by
Should the type for parameter $response not be null|Response?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
180
     * @return Response
181
     */
182
    public function render($view, array $parameters = array(), Response $response = null)
183
    {
184
        return parent::render(
185
            'SurfnetStepupGatewayGatewayBundle:Gateway:' . $view . '.html.twig',
186
            $parameters,
187
            $response
188
        );
189
    }
190
191
    /**
192
     * @return \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext
193
     */
194
    public function getResponseContext()
195
    {
196
        $stateHandler = $this->get('gateway.proxy.state_handler');
197
198
        $responseContextServiceId = $stateHandler->getResponseContextServiceId();
199
200
        if (!$responseContextServiceId) {
201
            return $this->get(static::RESPONSE_CONTEXT_SERVICE_ID);
202
        }
203
204
        return $this->get($responseContextServiceId);
205
    }
206
207
    /**
208
     * @param SAMLResponse $response
209
     * @return string
210
     */
211
    private function getResponseAsXML(SAMLResponse $response)
212
    {
213
        return base64_encode($response->toUnsignedXML()->ownerDocument->saveXML());
214
    }
215
216
    /**
217
     * @return SAMLResponse
218
     */
219
    private function createRequesterFailureResponse()
220
    {
221
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseBuilder $responseBuilder */
222
        $responseBuilder = $this->get('gateway.proxy.response_builder');
223
224
        $context = $this->getResponseContext();
225
226
        $response = $responseBuilder
227
            ->createNewResponse($context)
228
            ->setResponseStatus(Constants::STATUS_REQUESTER, Constants::STATUS_REQUEST_UNSUPPORTED)
229
            ->get();
230
231
        return $response;
232
    }
233
234
    /**
235
     * @param $context
236
     * @return SAMLResponse
237
     */
238
    private function createResponseFailureResponse($context)
239
    {
240
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseBuilder $responseBuilder */
241
        $responseBuilder = $this->get('gateway.proxy.response_builder');
242
243
        $response = $responseBuilder
244
            ->createNewResponse($context)
245
            ->setResponseStatus(Constants::STATUS_RESPONDER)
246
            ->get();
247
248
        return $response;
249
    }
250
251
    /**
252
     * @return LoginService
253
     */
254
    private function getGatewayLoginService()
255
    {
256
        return $this->get('gateway.service.gateway.login');
257
    }
258
259
    /**
260
     * @return ConsumeAssertionService
261
     */
262
    private function getGatewayConsumeAssertionService()
263
    {
264
        return $this->get('gateway.service.gateway.consume_assertion');
265
    }
266
267
    /**
268
     * @return RespondService
269
     */
270
    private function getGatewayRespondService()
271
    {
272
        return $this->get('gateway.service.gateway.respond');
273
    }
274
275
    /**
276
     * @return FailedResponseService
277
     */
278
    private function getGatewayFailedResponseService()
279
    {
280
        return $this->get('gateway.service.gateway.failed_response');
281
    }
282
283
}
284