Completed
Push — feature/second-factor-only-2 ( 161eea...f5c852 )
by Boy
07:57 queued 04:37
created

isNameIdAllowedToUseSecondFactorOnly()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
rs 9.3142
cc 3
eloc 12
nc 3
nop 3
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\SAML2\AuthnRequest;
24
use Surfnet\StepupBundle\Value\Loa;
25
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Saml\ResponseFactory;
26
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\LoaAliasLookupService;
27
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
28
use Symfony\Component\HttpFoundation\Request;
29
use Symfony\Component\HttpFoundation\Response;
30
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
31
32
class SecondFactorOnlyController extends Controller
33
{
34
    const RESPONSE_CONTEXT_SERVICE_ID = 'second_factor_only.response_context';
35
36
    /**
37
     * @param Request $httpRequest
38
     * @return Response
39
     */
40
    public function ssoAction(Request $httpRequest)
41
    {
42
        $logger = $this->get('logger');
43
44
        if (!$this->getParameter('second_factor_only')) {
45
            $logger->notice(sprintf(
46
                'Access to %s denied, second_factor_only parameter set to false.',
47
                __METHOD__
48
            ));
49
            throw $this->createAccessDeniedException('Second Factor Only feature disabled');
50
        }
51
52
        $logger->notice(
53
            'Received AuthnRequest on second-factor-only endpoint, started processing'
54
        );
55
56
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
57
        $redirectBinding = $this->get('second_factor_only.http.redirect_binding');
58
59
        try {
60
            $originalRequest = $redirectBinding->processSignedRequest($httpRequest);
61
        } catch (Exception $e) {
62
            $logger->critical(sprintf('Could not process Request, error: "%s"', $e->getMessage()));
63
64
            return $this->render(
65
                'SurfnetStepupGatewayGatewayBundle:Gateway:unrecoverableError.html.twig'
66
            );
67
        }
68
69
        $originalRequestId = $originalRequest->getRequestId();
70
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
71
        $logger->notice(sprintf(
72
            'AuthnRequest processing complete, received AuthnRequest from "%s", request ID: "%s"',
73
            $originalRequest->getServiceProvider(),
74
            $originalRequest->getRequestId()
75
        ));
76
77
        $stateHandler = $this->get('gateway.proxy.state_handler');
78
79
        $stateHandler
80
            ->setRequestId($originalRequestId)
81
            ->setRequestServiceProvider($originalRequest->getServiceProvider())
82
            ->setRelayState($httpRequest->get(AuthnRequest::PARAMETER_RELAY_STATE, ''))
83
            ->setResponseAction('SurfnetStepupGatewaySecondFactorOnlyBundle:SecondFactorOnly:respond')
84
            ->setResponseContextServiceId(static::RESPONSE_CONTEXT_SERVICE_ID);
85
86
        // Check if the NameID is provided and we may use it.
87
        $nameId = $originalRequest->getNameId();
88
        if (!$this->isNameIdAllowedToUseSecondFactorOnly($originalRequest->getServiceProvider(), $nameId, $logger)) {
89
            /** @var \Surfnet\StepupGateway\GatewayBundle\Service\ResponseRenderingService $responseRendering */
90
            $responseRendering = $this->get('second_factor_only.response_rendering');
91
            return $responseRendering->renderRequesterFailureResponse(
92
                $this->getResponseContext()
93
            );
94
        }
95
        $stateHandler->saveIdentityNameId($nameId);
96
97
        // Check if the requested Loa is provided and supported.
98
        $authnContextClassRef = $originalRequest->getAuthenticationContextClassRef();
99
        $loaId = $this->verifyAuthnContextClassRef($authnContextClassRef, $logger);
100
        if (!$loaId) {
101
            /** @var \Surfnet\StepupGateway\GatewayBundle\Service\ResponseRenderingService $responseRendering */
102
            $responseRendering = $this->get('second_factor_only.response_rendering');
103
            return $responseRendering->renderRequesterFailureResponse(
104
                $this->getResponseContext()
105
            );
106
        }
107
        $stateHandler->setRequiredLoaIdentifier($loaId);
108
109
        $logger->notice(
110
            'Forwarding to second factor controller for loa determination and handling'
111
        );
112
        return $this->forward(
113
            'SurfnetStepupGatewayGatewayBundle:SecondFactor:selectSecondFactorForVerification'
114
        );
115
    }
116
117
    /**
118
     * @param string $authnContextClassRef
119
     * @param LoggerInterface $logger
120
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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...
121
     */
122
    private function verifyAuthnContextClassRef(
123
        $authnContextClassRef,
124
        LoggerInterface $logger
125
    ) {
126
        if (!$authnContextClassRef) {
127
            $logger->info(
128
                'No LOA requested, sending response with status Requester Error'
129
            );
130
            return;
131
        }
132
133
        /** @var LoaAliasLookupService $loaAliasLookup */
134
        $loaAliasLookup = $this->get('second_factor_only.loa_alias_lookup');
135
        $loaId = $loaAliasLookup->findLoaIdByAlias($authnContextClassRef);
136
137
        if (!$loaId) {
138
            $logger->info(sprintf(
139
                'Requested required Loa "%s" does not have a second factor alias,'
140
                .' sending response with status Requester Error',
141
                $authnContextClassRef
142
            ));
143
            return;
144
        }
145
146
        $loaResolutionService = $this->get('surfnet_stepup.service.loa_resolution');
147
148
        if (!$loaResolutionService->hasLoa($loaId)) {
149
            $logger->info(sprintf(
150
                'Requested required Loa "%s" does not exist,'
151
                .' sending response with status Requester Error',
152
                $authnContextClassRef
153
            ));
154
            return;
155
        }
156
157
        return $loaId;
158
    }
159
160
    /**
161
     * @param string $spEntityId
162
     * @param string $nameId
163
     * @param LoggerInterface $logger
164
     * @return bool
165
     */
166
    private function isNameIdAllowedToUseSecondFactorOnly($spEntityId, $nameId, LoggerInterface $logger)
167
    {
168
        if (!$nameId) {
169
            $logger->info(
170
                'No NameID provided, sending response with status Requester Error'
171
            );
172
            return false;
173
        }
174
175
        $entityService = $this->get('second_factor_only.entity_service');
176
        $serviceProvider = $entityService->getServiceProvider($spEntityId);
177
178
        if (!$serviceProvider->isAllowedToUseSecondFactorOnlyFor($nameId)) {
179
            $logger->info(
180
                'No NameID provided, sending response with status Requester Error'
181
            );
182
            return false;
183
        }
184
185
        return true;
186
    }
187
188
    /**
189
     * @return Response
190
     */
191
    public function respondAction()
192
    {
193
        $responseContext = $this->getResponseContext();
194
        $originalRequestId = $responseContext->getInResponseTo();
195
196
        $logger = $this->get('surfnet_saml.logger')->forAuthentication($originalRequestId);
197
198
        if (!$this->getParameter('second_factor_only')) {
199
            $logger->notice(sprintf(
200
                'Access to %s denied, second_factor_only parameter set to false.',
201
                __METHOD__
202
            ));
203
            throw $this->createAccessDeniedException('Second Factor Only feature disabled');
204
        }
205
206
        $logger->notice('Creating second-factor-only Response');
207
208
        $selectedSecondFactorUuid = $this->getResponseContext()->getSelectedSecondFactor();
209
        if (!$selectedSecondFactorUuid) {
210
            $logger->error(
211
                'Cannot verify possession of an unknown second factor'
212
            );
213
214
            throw new BadRequestHttpException('Cannot verify possession of an unknown second factor.');
215
        }
216
217
        if (!$responseContext->isSecondFactorVerified()) {
218
            $logger->error('Second factor was not verified');
219
            throw new BadRequestHttpException(
220
                'Cannot verify possession of an unknown second factor.'
221
            );
222
        }
223
224
        $secondFactor = $this->get('gateway.service.second_factor_service')
225
            ->findByUuid($selectedSecondFactorUuid);
226
227
        $grantedLoa = $this->get('surfnet_stepup.service.loa_resolution')
228
            ->getLoaByLevel($secondFactor->getLoaLevel());
229
230
        /** @var LoaAliasLookupService $loaAliasLookup */
231
        $loaAliasLookup = $this->get('second_factor_only.loa_alias_lookup');
232
        $authnContextClassRef = $loaAliasLookup->findAliasByLoa($grantedLoa);
233
234
        /** @var ResponseFactory $response_factory */
235
        $responseFactory = $this->get('second_factor_only.saml_response_factory');
236
        $response = $responseFactory->createSecondFactorOnlyResponse(
237
            $responseContext->getIdentityNameId(),
238
            $responseContext->getServiceProvider(),
239
            $authnContextClassRef
240
        );
241
242
        $responseContext->responseSent();
243
244
        $logger->notice(sprintf(
245
            'Responding to request "%s" with newly created response "%s"',
246
            $responseContext->getInResponseTo(),
247
            $response->getId()
248
        ));
249
250
        $responseRendering = $this->get('second_factor_only.response_rendering');
251
        return $responseRendering->renderResponse($responseContext, $response);
252
    }
253
254
    /**
255
     * @return \Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext
256
     */
257
    public function getResponseContext()
258
    {
259
        return $this->get(static::RESPONSE_CONTEXT_SERVICE_ID);
260
    }
261
}
262