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

verifyAuthnContextClassRef()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 46
Code Lines 30

Duplication

Lines 23
Ratio 50 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 23
loc 46
rs 8.4751
cc 5
eloc 30
nc 4
nop 2
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 Psr\Log\LoggerInterface;
23
use Surfnet\SamlBundle\Http\XMLResponse;
24
use Surfnet\SamlBundle\SAML2\AuthnRequest;
25
use Surfnet\StepupBundle\Value\AuthnContextClass;
26
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\HttpFoundation\Response;
29
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
30
31
class SecondFactorOnlyController extends Controller
32
{
33
    const RESPONSE_CONTEXT_SERVICE_ID = 'second_factor_only.response_context';
34
35
    /**
36
     * @return XMLResponse
37
     */
38
    public function metadataAction()
39
    {
40
        return new XMLResponse(
41
          $this->get('second_factor_only.metadata_factory')->generate()
42
        );
43
    }
44
45
    /**
46
     * @param Request $httpRequest
47
     * @return Response
48
     */
49
    public function ssoAction(Request $httpRequest)
50
    {
51
        $logger = $this->get('logger');
52
        $logger->notice('Received AuthnRequest, started processing');
53
54
        $redirectBinding = $this->get('second_factor_only.http.redirect_binding');
55
56
        try {
57
            $originalRequest = $redirectBinding->processRequest($httpRequest);
58
        } catch (Exception $e) {
59
            $logger->critical(sprintf('Could not process Request, error: "%s"', $e->getMessage()));
60
61
            return $this->render(
62
              'SurfnetStepupGatewayGatewayBundle:Gateway:unrecoverableError.html.twig'
63
            );
64
        }
65
66
        $originalRequestId = $originalRequest->getRequestId();
67
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
68
        $logger->notice(sprintf(
69
          'AuthnRequest processing complete, received AuthnRequest from "%s", request ID: "%s"',
70
          $originalRequest->getServiceProvider(),
71
          $originalRequest->getRequestId()
72
        ));
73
74
        $stateHandler = $this->get('gateway.proxy.state_handler');
75
76
        $stateHandler
77
          ->setRequestId($originalRequestId)
78
          ->setRequestServiceProvider($originalRequest->getServiceProvider())
79
          ->setRelayState($httpRequest->get(AuthnRequest::PARAMETER_RELAY_STATE, ''))
80
          ->setResponseAction('SurfnetStepupGatewaySecondFactorOnlyBundle:SecondFactorOnly:respond')
81
          ->setResponseContextServiceId(static::RESPONSE_CONTEXT_SERVICE_ID);
82
83
        if (!$originalRequest->getNameId()) {
84
            $logger->info(
85
              'No NameID provided, sending response with status Requester Error'
86
            );
87
            $responseRendering = $this->get('gateway.service.saml_response');
88
            return $responseRendering->renderRequesterFailureResponse(
89
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
90
            );
91
        }
92
93
        $stateHandler->saveIdentityNameId($originalRequest->getNameId());
94
95
        // check if the requested Loa is supported
96
        $authnContextClassRef = $originalRequest->getAuthenticationContextClassRef();
97
        $failureResponse = $this->verifyAuthnContextClassRef(
98
          $authnContextClassRef,
99
          $logger
100
        );
101
102
        if ($failureResponse) {
103
            return $failureResponse;
104
        }
105
106
        $stateHandler->setRequestAuthnContextClassRef(
107
          $originalRequest->getAuthenticationContextClassRef()
108
        );
109
110
        $logger->notice(
111
          'Forwarding to second factor controller for loa determination and handling'
112
        );
113
114
        return $this->forward(
115
          'SurfnetStepupGatewayGatewayBundle:Selection:selectSecondFactorForVerification'
116
        );
117
    }
118
119
    /**
120
     * @return Response
121
     */
122
    public function respondAction()
123
    {
124
        $responseContext = $this->get(static::RESPONSE_CONTEXT_SERVICE_ID);
125
        $originalRequestId = $responseContext->getInResponseTo();
126
127
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
128
        $logger->notice('Creating Response');
129
130
        $secondFactorUuid = $this->get('gateway.service.require_selected_factor')
131
          ->requireSelectedSecondFactor($logger);
132
133
        if (!$responseContext->isSecondFactorVerified()) {
134
            $logger->error('Second factor was not verified');
135
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
136
        }
137
138
        $secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid(
139
          $secondFactorUuid
140
        );
141
142
        $grantedLoa = $this->get('surfnet_stepup.service.loa_resolution')->getLoaByLevel(
143
          $secondFactor->getLoaLevel()
144
        );
145
146
        $authnContextClass = $grantedLoa->fetchAuthnContextClassOfType(
147
          AuthnContextClass::TYPE_SECOND_FACTOR_ONLY
148
        );
149
150
        $response = $this->get('second_factor_only.response_proxy')
151
          ->createSecondFactorOnlyResponse(
152
              $responseContext->getIdentityNameId(),
153
              $responseContext->getServiceProvider(),
154
              (string) $authnContextClass
155
        );
156
157
        $responseContext->responseSent();
158
159
        $logger->notice(sprintf(
160
          'Responding to request "%s" with response based on '
161
          . 'response from the remote IdP with response "%s"',
162
          $responseContext->getInResponseTo(),
163
          $response->getId()
164
        ));
165
166
        $responseRendering = $this->get('gateway.service.saml_response');
167
        return $responseRendering->renderResponse($responseContext, $response);
168
    }
169
170
    /**
171
     * @param string $authnContextClassRef
172
     * @param LoggerInterface $logger
173
     * @return Response
174
     */
175
    private function verifyAuthnContextClassRef(
176
      $authnContextClassRef,
177
      LoggerInterface $logger
178
    ) {
179
        if (!$authnContextClassRef) {
180
            $logger->info(
181
              'No LOA requested, sending response with status Requester Error'
182
            );
183
            $responseRendering = $this->get('gateway.service.saml_response');
184
            return $responseRendering->renderRequesterFailureResponse(
185
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
186
            );
187
        }
188
189
        $loaResolutionService = $this->get('surfnet_stepup.service.loa_resolution');
190
        $loa = $loaResolutionService->getLoa($authnContextClassRef);
191
192 View Code Duplication
        if (!$loa) {
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...
193
            $logger->info(sprintf(
194
              'Requested required Loa "%s" does not exist,'
195
              .' sending response with status Requester Error',
196
              $authnContextClassRef
197
            ));
198
            $responseRendering = $this->get('gateway.service.saml_response');
199
            return $responseRendering->renderRequesterFailureResponse(
200
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
201
            );
202
        }
203
204
        $expectedContextClass = $loa->fetchAuthnContextClassOfType(
205
          AuthnContextClass::TYPE_SECOND_FACTOR_ONLY
206
        );
207
208 View Code Duplication
        if (!$expectedContextClass || !$expectedContextClass->isIdentifiedBy($authnContextClassRef)) {
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...
209
            $logger->info(sprintf(
210
              'Requested required Loa "%s" does is of the wrong type!'
211
              . ' Please use second-factor-only AuthnContextClassRefs.'
212
              . ' Sending response with status Requester Error',
213
              $authnContextClassRef
214
            ));
215
            $responseRendering = $this->get('gateway.service.saml_response');
216
            return $responseRendering->renderRequesterFailureResponse(
217
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
218
            );
219
        }
220
    }
221
}
222