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

verifyAuthnContextClassRef()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 39
rs 8.5806
cc 4
eloc 25
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(
53
          'Received AuthnRequest on second-factor-only endpoint, started processing'
54
        );
55
56
        $redirectBinding = $this->get('second_factor_only.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
        $stateHandler = $this->get('gateway.proxy.state_handler');
77
78
        $stateHandler
79
          ->setRequestId($originalRequestId)
80
          ->setRequestServiceProvider($originalRequest->getServiceProvider())
81
          ->setRelayState($httpRequest->get(AuthnRequest::PARAMETER_RELAY_STATE, ''))
82
          ->setResponseAction('SurfnetStepupGatewaySecondFactorOnlyBundle:SecondFactorOnly:respond')
83
          ->setResponseContextServiceId(static::RESPONSE_CONTEXT_SERVICE_ID);
84
85
        // Check if the NameID is provided and we may use it.
86
        $nameId = $originalRequest->getNameId();
87
        if (!$this->verifyNameId($originalRequest->getServiceProvider(), $nameId, $logger)) {
88
            $responseRendering = $this->get('gateway.service.saml_response');
89
            return $responseRendering->renderRequesterFailureResponse(
90
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
91
            );
92
        }
93
        $stateHandler->saveIdentityNameId($nameId);
94
95
        // Check if the requested Loa is provided and supported.
96
        $authnContextClassRef = $originalRequest->getAuthenticationContextClassRef();
97
        if (!$this->verifyAuthnContextClassRef($authnContextClassRef, $logger)) {
98
            $responseRendering = $this->get('gateway.service.saml_response');
99
            return $responseRendering->renderRequesterFailureResponse(
100
              $this->get(static::RESPONSE_CONTEXT_SERVICE_ID)
101
            );
102
        }
103
        $stateHandler->setRequestAuthnContextClassRef($authnContextClassRef);
104
105
        $logger->notice(
106
          'Forwarding to second factor controller for loa determination and handling'
107
        );
108
        return $this->forward(
109
          'SurfnetStepupGatewayGatewayBundle:Selection:selectSecondFactorForVerification'
110
        );
111
    }
112
113
    /**
114
     * @param string $authnContextClassRef
115
     * @param LoggerInterface $logger
116
     * @return Response
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
117
     */
118
    private function verifyAuthnContextClassRef(
119
      $authnContextClassRef,
120
      LoggerInterface $logger
121
    ) {
122
        if (!$authnContextClassRef) {
123
            $logger->info(
124
              'No LOA requested, sending response with status Requester Error'
125
            );
126
            return false;
127
        }
128
129
        $loaResolutionService = $this->get('surfnet_stepup.service.loa_resolution');
130
        $loa = $loaResolutionService->getLoa($authnContextClassRef);
131
132
        if (!$loa) {
133
            $logger->info(sprintf(
134
              'Requested required Loa "%s" does not exist,'
135
              .' sending response with status Requester Error',
136
              $authnContextClassRef
137
            ));
138
            return false;
139
        }
140
141
        $expectedContextClass = $loa->fetchAuthnContextClassOfType(
142
          AuthnContextClass::TYPE_SECOND_FACTOR_ONLY
143
        );
144
145
        if (!$expectedContextClass) {
146
            $logger->info(sprintf(
147
              'Requested required Loa "%s" does is of the wrong type!'
148
              . ' Please use second-factor-only AuthnContextClassRefs.'
149
              . ' Sending response with status Requester Error',
150
              $authnContextClassRef
151
            ));
152
            return false;
153
        }
154
155
        return true;
156
    }
157
158
    /**
159
     * @param string $spEntityId
160
     * @param string $nameId
161
     * @param LoggerInterface $logger
162
     * @return bool
163
     */
164
    private function verifyNameId($spEntityId, $nameId, LoggerInterface $logger)
165
    {
166
        if (!$nameId) {
167
            $logger->info(
168
              'No NameID provided, sending response with status Requester Error'
169
            );
170
            return false;
171
        }
172
173
        $entityService = $this->get('second_factor_only.entity_service');
174
        $serviceProvider = $entityService->getServiceProvider($spEntityId);
175
176
        if (!$serviceProvider->isAllowedToUseSecondFactorOnlyFor($nameId)) {
177
            $logger->info(
178
              'No NameID provided, sending response with status Requester Error'
179
            );
180
            return false;
181
        }
182
183
        return true;
184
    }
185
186
    /**
187
     * @return Response
188
     */
189
    public function respondAction()
190
    {
191
        $responseContext = $this->get(static::RESPONSE_CONTEXT_SERVICE_ID);
192
        $originalRequestId = $responseContext->getInResponseTo();
193
194
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
195
        $logger->notice('Creating second-factor-only Response');
196
197
        $secondFactorUuid = $this->get('gateway.service.require_selected_factor')
198
          ->requireSelectedSecondFactor($logger);
199
200
        if (!$responseContext->isSecondFactorVerified()) {
201
            $logger->error('Second factor was not verified');
202
            throw new BadRequestHttpException(
203
              'Cannot verify possession of an unknown second factor.'
204
            );
205
        }
206
207
        $secondFactor = $this->get('gateway.service.second_factor_service')
208
          ->findByUuid($secondFactorUuid);
209
210
        $grantedLoa = $this->get('surfnet_stepup.service.loa_resolution')
211
          ->getLoaByLevel($secondFactor->getLoaLevel());
212
213
        $authnContextClass = $grantedLoa->fetchAuthnContextClassOfType(
214
          AuthnContextClass::TYPE_SECOND_FACTOR_ONLY
215
        );
216
217
        $response = $this->get('second_factor_only.response_proxy')
218
          ->createSecondFactorOnlyResponse(
219
              $responseContext->getIdentityNameId(),
220
              $responseContext->getServiceProvider(),
221
              (string) $authnContextClass
222
        );
223
224
        $responseContext->responseSent();
225
226
        $logger->notice(sprintf(
227
          'Responding to request "%s" with newly created response "%s"',
228
          $responseContext->getInResponseTo(),
229
          $response->getId()
230
        ));
231
232
        $responseRendering = $this->get('gateway.service.saml_response');
233
        return $responseRendering->renderResponse($responseContext, $response);
234
    }
235
}
236