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

SecondFactorOnlyController::ssoAction()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 76
Code Lines 46

Duplication

Lines 7
Ratio 9.21 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 7
loc 76
rs 8.4596
cc 5
eloc 46
nc 5
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 View Code Duplication
        if (!$this->get('service_container')->getParameter('second_factor_only')) {
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...
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);
0 ignored issues
show
Bug introduced by
The method processSignedRequest() 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...
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->verifyNameId($originalRequest->getServiceProvider(), $nameId, $logger)) {
89
            /** @var \Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\ResponseRenderingService $responseRendering */
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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\SecondFactorOnlyBundle\Service\ResponseRenderingService $responseRendering */
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
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 verifyNameId($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 View Code Duplication
        if (!$this->get('service_container')->getParameter('second_factor_only')) {
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...
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