Completed
Pull Request — develop (#92)
by Boy
02:51
created

GatewayBundle/Controller/GatewayController.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 Exception;
22
use SAML2_Assertion;
23
use SAML2_Const;
24
use SAML2_Response;
25
use Surfnet\SamlBundle\SAML2\AuthnRequest;
26
use Surfnet\SamlBundle\SAML2\AuthnRequestFactory;
27
use Surfnet\StepupGateway\GatewayBundle\Saml\AssertionAdapter;
28
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
29
use Symfony\Component\HttpFoundation\Request;
30
use Symfony\Component\HttpFoundation\Response;
31
use Symfony\Component\HttpKernel\Exception\HttpException;
32
33
class GatewayController extends Controller
34
{
35
    public function ssoAction(Request $httpRequest)
36
    {
37
        /** @var \Psr\Log\LoggerInterface $logger */
38
        $logger = $this->get('logger');
39
        $logger->notice('Received AuthnRequest, started processing');
40
41
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
42
        $redirectBinding = $this->get('surfnet_saml.http.redirect_binding');
43
44
        try {
45
            $originalRequest = $redirectBinding->processUnsignedRequest($httpRequest);
0 ignored issues
show
The method processUnsignedRequest() does not seem to exist on object<Surfnet\SamlBundle\Http\RedirectBinding>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
46
        } catch (Exception $e) {
47
            $logger->critical(sprintf('Could not process Request, error: "%s"', $e->getMessage()));
48
49
            return $this->render('unrecoverableError');
50
        }
51
52
        $originalRequestId = $originalRequest->getRequestId();
53
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
54
        $logger->notice(sprintf(
55
            'AuthnRequest processing complete, received AuthnRequest from "%s", request ID: "%s"',
56
            $originalRequest->getServiceProvider(),
57
            $originalRequest->getRequestId()
58
        ));
59
60
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler $stateHandler */
61
        $stateHandler = $this->get('gateway.proxy.state_handler');
62
        $stateHandler
63
            ->setRequestId($originalRequestId)
64
            ->setRequestServiceProvider($originalRequest->getServiceProvider())
65
            ->setRelayState($httpRequest->get(AuthnRequest::PARAMETER_RELAY_STATE, ''));
66
67
        // check if the requested Loa is supported
68
        $authnContextClassRef = $originalRequest->getAuthenticationContextClassRef();
69
        if ($authnContextClassRef) {
70
            $requiredLoaIdentifier = $this->get('gateway.loa_domain')->findLoaIdByAuthnContextClassRef(
71
                $authnContextClassRef
72
            );
73
74
            if (!$requiredLoaIdentifier) {
75
                $logger->info(sprintf(
76
                    'Requested required Loa "%s" does not exist, sending response with status Requester Error',
77
                    $requiredLoaIdentifier
78
                ));
79
80
                $response = $this->createRequesterFailureResponse();
81
82
                return $this->renderSamlResponse('consumeAssertion', $response);
83
            }
84
85
            $stateHandler->setRequiredLoaIdentifier($requiredLoaIdentifier);
86
        }
87
88
        $proxyRequest = AuthnRequestFactory::createNewRequest(
89
            $this->get('surfnet_saml.hosted.service_provider'),
90
            $this->get('surfnet_saml.remote.idp')
91
        );
92
93
        $proxyRequest->setScoping([$originalRequest->getServiceProvider()]);
94
        $stateHandler->setGatewayRequestId($proxyRequest->getRequestId());
95
96
        $logger->notice(sprintf(
97
            'Sending Proxy AuthnRequest with request ID: "%s" for original AuthnRequest "%s"',
98
            $proxyRequest->getRequestId(),
99
            $originalRequest->getRequestId()
100
        ));
101
102
        return $redirectBinding->createRedirectResponseFor($proxyRequest);
103
    }
104
105
    public function proxySsoAction()
106
    {
107
        throw new HttpException(418, 'Not Yet Implemented');
108
    }
109
110
    /**
111
     * @param Request $request
112
     * @return \Symfony\Component\HttpFoundation\Response
113
     */
114
    public function consumeAssertionAction(Request $request)
115
    {
116
        $responseContext = $this->getResponseContext();
117
        $originalRequestId = $responseContext->getInResponseTo();
118
119
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
120
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
121
        $logger->notice('Received SAMLResponse, attempting to process for Proxy Response');
122
123
        try {
124
            /** @var \SAML2_Assertion $assertion */
125
            $assertion = $this->get('surfnet_saml.http.post_binding')->processResponse(
126
                $request,
127
                $this->get('surfnet_saml.remote.idp'),
128
                $this->get('surfnet_saml.hosted.service_provider')
129
            );
130
        } catch (Exception $exception) {
131
            $logger->error(sprintf('Could not process received Response, error: "%s"', $exception->getMessage()));
132
133
            $response = $this->createResponseFailureResponse($responseContext);
134
135
            return $this->renderSamlResponse('unprocessableResponse', $response);
136
        }
137
138
        $adaptedAssertion = new AssertionAdapter($assertion);
139
        $expectedInResponseTo = $responseContext->getExpectedInResponseTo();
140 View Code Duplication
        if (!$adaptedAssertion->inResponseToMatches($expectedInResponseTo)) {
141
            $logger->critical(sprintf(
142
                'Received Response with unexpected InResponseTo: "%s", %s',
143
                $adaptedAssertion->getInResponseTo(),
144
                ($expectedInResponseTo ? 'expected "' . $expectedInResponseTo . '"' : ' no response expected')
145
            ));
146
147
            return $this->render('unrecoverableError');
148
        }
149
150
        $logger->notice('Successfully processed SAMLResponse');
151
152
        $responseContext->saveAssertion($assertion);
153
154
        $logger->notice(sprintf('Forwarding to second factor controller for loa determination and handling'));
155
156
        return $this->forward('SurfnetStepupGatewayGatewayBundle:SecondFactor:selectSecondFactorForVerification');
157
    }
158
159
    public function respondAction()
160
    {
161
        $responseContext = $this->getResponseContext();
162
        $originalRequestId = $responseContext->getInResponseTo();
163
164
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
165
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
166
        $logger->notice('Creating Response');
167
168
        $grantedLoa = null;
169
        if ($responseContext->isSecondFactorVerified()) {
170
            $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid(
171
                $responseContext->getSelectedSecondFactor()
172
            );
173
174
            $grantedLoa = $this->get('surfnet_stepup.service.loa_resolution')->getLoaByLevel(
175
                $secondFactor->getLoaLevel()
176
            );
177
        }
178
179
        /** @var \Surfnet\StepupGateway\GatewayBundle\Service\ProxyResponseService $proxyResponseService */
180
        $proxyResponseService = $this->get('gateway.service.response_proxy');
181
        $response             = $proxyResponseService->createProxyResponse(
182
            $responseContext->reconstituteAssertion(),
183
            $responseContext->getServiceProvider(),
184
            (string) $grantedLoa
185
        );
186
187
        $responseContext->responseSent();
188
189
        $logger->notice(sprintf(
190
            'Responding to request "%s" with response based on response from the remote IdP with response "%s"',
191
            $responseContext->getInResponseTo(),
192
            $response->getId()
193
        ));
194
195
        return $this->renderSamlResponse('consumeAssertion', $response);
196
    }
197
198 View Code Duplication
    public function sendLoaCannotBeGivenAction()
199
    {
200
        $responseContext = $this->getResponseContext();
201
        $originalRequestId = $responseContext->getInResponseTo();
202
203
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
204
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
205
        $logger->notice('Loa cannot be given, creating Response with NoAuthnContext status');
206
207
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseBuilder $responseBuilder */
208
        $responseBuilder = $this->get('gateway.proxy.response_builder');
209
210
        $response = $responseBuilder
211
            ->createNewResponse($responseContext)
212
            ->setResponseStatus(SAML2_Const::STATUS_RESPONDER, SAML2_Const::STATUS_NO_AUTHN_CONTEXT)
213
            ->get();
214
215
        $logger->notice(sprintf(
216
            'Responding to request "%s" with response based on response from the remote IdP with response "%s"',
217
            $responseContext->getInResponseTo(),
218
            $response->getId()
219
        ));
220
221
        return $this->renderSamlResponse('consumeAssertion', $response);
222
    }
223
224 View Code Duplication
    public function sendAuthenticationCancelledByUserAction()
225
    {
226
        $responseContext = $this->getResponseContext();
227
        $originalRequestId = $responseContext->getInResponseTo();
228
229
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
230
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
231
        $logger->notice('Authentication was cancelled by the user, creating Response with AuthnFailed status');
232
233
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseBuilder $responseBuilder */
234
        $responseBuilder = $this->get('gateway.proxy.response_builder');
235
236
        $response = $responseBuilder
237
            ->createNewResponse($responseContext)
238
            ->setResponseStatus(
239
                SAML2_Const::STATUS_RESPONDER,
240
                SAML2_Const::STATUS_AUTHN_FAILED,
241
                'Authentication cancelled by user'
242
            )
243
            ->get();
244
245
        $logger->notice(sprintf(
246
            'Responding to request "%s" with response based on response from the remote IdP with response "%s"',
247
            $responseContext->getInResponseTo(),
248
            $response->getId()
249
        ));
250
251
        return $this->renderSamlResponse('consumeAssertion', $response);
252
    }
253
254
    /**
255
     * @param string         $view
256
     * @param SAML2_Response $response
257
     * @return Response
258
     */
259 View Code Duplication
    public function renderSamlResponse($view, SAML2_Response $response)
260
    {
261
        $responseContext = $this->getResponseContext();
262
263
        return $this->render($view, [
264
            'acu'        => $responseContext->getDestination(),
265
            'response'   => $this->getResponseAsXML($response),
266
            'relayState' => $responseContext->getRelayState()
267
        ]);
268
    }
269
270
    /**
271
     * @param string   $view
272
     * @param array    $parameters
273
     * @param Response $response
274
     * @return Response
275
     */
276
    public function render($view, array $parameters = array(), Response $response = null)
277
    {
278
        return parent::render(
279
            'SurfnetStepupGatewayGatewayBundle:Gateway:' . $view . '.html.twig',
280
            $parameters,
281
            $response
282
        );
283
    }
284
285
    /**
286
     * @return \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext
287
     */
288
    public function getResponseContext()
289
    {
290
        return $this->get('gateway.proxy.response_context');
291
    }
292
293
    /**
294
     * @param SAML2_Response $response
295
     * @return string
296
     */
297
    private function getResponseAsXML(SAML2_Response $response)
298
    {
299
        return base64_encode($response->toUnsignedXML()->ownerDocument->saveXML());
300
    }
301
302
    /**
303
     * @return SAML2_Response
304
     */
305
    private function createRequesterFailureResponse()
306
    {
307
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseBuilder $responseBuilder */
308
        $responseBuilder = $this->get('gateway.proxy.response_builder');
309
        $context = $this->getResponseContext();
310
311
        $response = $responseBuilder
312
            ->createNewResponse($context)
313
            ->setResponseStatus(SAML2_Const::STATUS_REQUESTER, SAML2_Const::STATUS_REQUEST_UNSUPPORTED)
314
            ->get();
315
316
        return $response;
317
318
    }
319
320
    /**
321
     * @param $context
322
     * @return SAML2_Response
323
     */
324
    private function createResponseFailureResponse($context)
325
    {
326
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseBuilder $responseBuilder */
327
        $responseBuilder = $this->get('gateway.proxy.response_builder');
328
329
        $response = $responseBuilder
330
            ->createNewResponse($context)
331
            ->setResponseStatus(SAML2_Const::STATUS_RESPONDER)
332
            ->get();
333
334
        return $response;
335
    }
336
}
337