Completed
Pull Request — develop (#91)
by Boy
19:20
created

GatewayController::sendLoaCannotBeGivenAction()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 15

Duplication

Lines 25
Ratio 100 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 25
loc 25
rs 8.8571
cc 1
eloc 15
nc 1
nop 0
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 Psr\Log\LoggerInterface;
23
use SAML2_Assertion;
24
use SAML2_Const;
25
use SAML2_Response;
26
use Surfnet\SamlBundle\Http\XMLResponse;
27
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
28
use Surfnet\SamlBundle\SAML2\AuthnRequest;
29
use Surfnet\SamlBundle\SAML2\AuthnRequestFactory;
30
use Surfnet\StepupBundle\Value\AuthnContextClass;
31
use Surfnet\StepupGateway\GatewayBundle\Saml\AssertionAdapter;
32
use Surfnet\StepupGateway\GatewayBundle\Service\ProxyResponseService;
33
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
34
use Symfony\Component\HttpFoundation\Request;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\HttpKernel\Exception\HttpException;
37
38
class GatewayController extends Controller
39
{
40
    const RESPONSE_CONTEXT_SERVICE_ID = 'gateway.proxy.response_context';
41
42
    public function metadataAction()
43
    {
44
        return new XMLResponse(
45
          $this->get('surfnet_saml.metadata_factory')->generate()
46
        );
47
    }
48
49
    public function ssoAction(Request $httpRequest)
50
    {
51
        /** @var \Psr\Log\LoggerInterface $logger */
52
        $logger = $this->get('logger');
53
        $logger->notice('Received AuthnRequest, started processing');
54
55
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
56
        $redirectBinding = $this->get('surfnet_saml.http.redirect_binding');
57
58
        try {
59
            $originalRequest = $redirectBinding->processRequest($httpRequest);
60
        } catch (Exception $e) {
61
            $logger->critical(sprintf('Could not process Request, error: "%s"', $e->getMessage()));
62
63
            return $this->render(
64
              'SurfnetStepupGatewayGatewayBundle:Gateway:unrecoverableError.html.twig'
65
            );
66
        }
67
68
        $originalRequestId = $originalRequest->getRequestId();
69
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
70
        $logger->notice(sprintf(
71
            'AuthnRequest processing complete, received AuthnRequest from "%s", request ID: "%s"',
72
            $originalRequest->getServiceProvider(),
73
            $originalRequest->getRequestId()
74
        ));
75
76
        /** @var \Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler $stateHandler */
77
        $stateHandler = $this->get('gateway.proxy.state_handler');
78
        $stateHandler
79
            ->setRequestId($originalRequestId)
80
            ->setRequestServiceProvider($originalRequest->getServiceProvider())
81
            ->setRelayState($httpRequest->get(AuthnRequest::PARAMETER_RELAY_STATE, ''))
82
            ->setResponseAction('SurfnetStepupGatewayGatewayBundle:Gateway:respond')
83
            ->setResponseContextServiceId(static::RESPONSE_CONTEXT_SERVICE_ID);
84
85
        // check if the requested Loa is supported
86
        $authnContextClassRef = $originalRequest->getAuthenticationContextClassRef();
87
        $failureResponse = $this->verifyAuthnContextClassRef(
88
          $authnContextClassRef,
89
          $logger
90
        );
91
92
        if ($failureResponse) {
93
            return $failureResponse;
94
        }
95
96
        $stateHandler->setRequestAuthnContextClassRef($authnContextClassRef);
97
98
        $proxyRequest = AuthnRequestFactory::createNewRequest(
99
            $this->get('surfnet_saml.hosted.service_provider'),
100
            $this->get('surfnet_saml.remote.idp')
101
        );
102
103
        $proxyRequest->setScoping([$originalRequest->getServiceProvider()]);
104
        $stateHandler->setGatewayRequestId($proxyRequest->getRequestId());
105
106
        $logger->notice(sprintf(
107
            'Sending Proxy AuthnRequest with request ID: "%s" for original AuthnRequest "%s"',
108
            $proxyRequest->getRequestId(),
109
            $originalRequest->getRequestId()
110
        ));
111
112
        return $redirectBinding->createRedirectResponseFor($proxyRequest);
113
    }
114
115
    public function proxySsoAction()
116
    {
117
        throw new HttpException(418, 'Not Yet Implemented');
118
    }
119
120
    /**
121
     * @param Request $request
122
     * @return \Symfony\Component\HttpFoundation\Response
123
     */
124
    public function consumeAssertionAction(Request $request)
125
    {
126
        $responseContext = $this->get(static::RESPONSE_CONTEXT_SERVICE_ID);
127
        $originalRequestId = $responseContext->getInResponseTo();
128
129
        /** @var \Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger $logger */
130
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
131
        $logger->notice('Received SAMLResponse, attempting to process for Proxy Response');
132
133
        try {
134
            $assertion = $this->get('surfnet_saml.http.post_binding')->processResponse(
135
                $request,
136
                $this->get('surfnet_saml.remote.idp'),
137
                $this->get('surfnet_saml.hosted.service_provider')
138
            );
139
        } catch (Exception $exception) {
140
            $logger->error(
141
              sprintf(
142
                'Could not process received Response, error: "%s"',
143
                $exception->getMessage()
144
              )
145
            );
146
            $responseRendering = $this->get('gateway.service.saml_response');
147
            return $responseRendering->renderUnprocessableResponse(
148
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
149
            );
150
        }
151
152
        $adaptedAssertion = new AssertionAdapter($assertion);
153
        $expectedInResponseTo = $responseContext->getExpectedInResponseTo();
154 View Code Duplication
        if (!$adaptedAssertion->inResponseToMatches($expectedInResponseTo)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
155
            $logger->critical(sprintf(
156
                'Received Response with unexpected InResponseTo: "%s", %s',
157
                $adaptedAssertion->getInResponseTo(),
158
                ($expectedInResponseTo ? 'expected "' . $expectedInResponseTo . '"' : ' no response expected')
159
            ));
160
161
            return $this->render(
162
              'SurfnetStepupGatewayGatewayBundle:Gateway:unrecoverableError.html.twig'
163
            );
164
        }
165
166
        $logger->notice('Successfully processed SAMLResponse');
167
168
        $responseContext->saveAssertion($assertion);
169
170
        $logger->notice(sprintf('Forwarding to second factor controller for loa determination and handling'));
171
172
        return $this->forward(
173
          'SurfnetStepupGatewayGatewayBundle:Selection:selectSecondFactorForVerification'
174
        );
175
    }
176
177
    public function respondAction()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
178
    {
179
        $responseContext = $this->get(static::RESPONSE_CONTEXT_SERVICE_ID);
180
        $originalRequestId = $responseContext->getInResponseTo();
181
182
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
183
        $logger->notice('Creating Response');
184
185
        $grantedLoa = null;
186
        if ($responseContext->isSecondFactorVerified()) {
187
            $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid(
188
                $responseContext->getSelectedSecondFactor()
189
            );
190
191
            $grantedLoa = $this->get('surfnet_stepup.service.loa_resolution')->getLoaByLevel(
192
                $secondFactor->getLoaLevel()
193
            );
194
        }
195
196
        $proxyResponseService = $this->get('gateway.service.response_proxy');
197
        $response             = $proxyResponseService->createProxyResponse(
198
            $responseContext->reconstituteAssertion(),
199
            $responseContext->getServiceProvider(),
200
            (string) $grantedLoa
201
        );
202
203
        $responseContext->responseSent();
204
205
        $logger->notice(sprintf(
206
            'Responding to request "%s" with response based on response from the remote IdP with response "%s"',
207
            $responseContext->getInResponseTo(),
208
            $response->getId()
209
        ));
210
211
        $responseRendering = $this->get('gateway.service.saml_response');
212
        return $responseRendering->renderResponse($responseContext, $response);
213
    }
214
215
    /**
216
     * @param $authnContextClassRef
217
     * @param LoggerInterface $logger
218
     * @return null|Response
219
     */
220
    private function verifyAuthnContextClassRef(
221
      $authnContextClassRef,
222
      LoggerInterface $logger
223
    ) {
224
        if (!$authnContextClassRef) {
225
            return null;
226
        }
227
228
        $loaResolution = $this->get('surfnet_stepup.service.loa_resolution');
229
        if (!$loaResolution->hasLoa($authnContextClassRef)) {
230
            $logger->info(
231
              sprintf(
232
                'Requested required Loa "%s" does not exist, sending response with status Requester Error',
233
                $authnContextClassRef
234
              )
235
            );
236
            $responseRendering = $this->get(
237
              'gateway.service.saml_response'
238
            );
239
240
            return $responseRendering->renderRequesterFailureResponse(
241
              $this->get(self::RESPONSE_CONTEXT_SERVICE_ID)
242
            );
243
        }
244
245
        $loa = $loaResolution->getLoa($authnContextClassRef);
246
        $authContextClass = $loa->fetchAuthnContextClassOfType(
247
          AuthnContextClass::TYPE_GATEWAY
248
        );
249
250
        if (!$authContextClass->isIdentifiedBy($authnContextClassRef)) {
251
            $logger->info(
252
              sprintf(
253
                'Requested required Loa "%s" is of the wrong type, sending response with status Requester Error',
254
                $authnContextClassRef
255
              )
256
            );
257
            $responseRendering = $this->get(
258
              'gateway.service.saml_response'
259
            );
260
261
            return $responseRendering->renderRequesterFailureResponse(
262
              $this->get(self::RESPONSE_CONTEXT_SERVICE_ID)
263
            );
264
        }
265
266
        return null;
267
    }
268
}
269