GssfController::verifyAction()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 76
rs 7.9014
c 0
b 0
f 0
cc 6
nc 6
nop 2

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\StepupRa\RaBundle\Controller\Vetting;
20
21
use Exception;
22
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
23
use Surfnet\SamlBundle\Http\XMLResponse;
24
use Surfnet\SamlBundle\SAML2\AuthnRequestFactory;
25
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
26
use Surfnet\StepupRa\RaBundle\Exception\RuntimeException;
27
use Surfnet\StepupRa\RaBundle\Form\Type\InitiateGssfType;
28
use Surfnet\StepupRa\RaBundle\Service\VettingService;
29
use Symfony\Component\HttpFoundation\Request;
30
use Symfony\Component\HttpFoundation\Response;
31
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
32
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
33
34
/**
35
 * Orchestrates verification of GSSFs (Generic SAML Second Factors) through GSSPs (Generic SAML Stepup Providers).
36
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
37
 */
38
final class GssfController extends SecondFactorController
39
{
40
    /**
41
     * Initiates verification of a GSSF.
42
     *
43
     * @Template
44
     * @param string $procedureId
45
     * @param string $provider
46
     * @return array|Response
47
     */
48
    public function initiateAction($procedureId, $provider)
49
    {
50
        $this->assertSecondFactorEnabled($provider);
51
52
        $this->denyAccessUnlessGranted(['ROLE_RA']);
53
54
        $logger = $this->get('ra.procedure_logger')->forProcedure($procedureId);
55
        $logger->notice('Showing Initiate GSSF Verification Screen', ['provider' => $provider]);
56
57 View Code Duplication
        if (!$this->getVettingService()->hasProcedure($procedureId)) {
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...
58
            $logger->notice(sprintf('Vetting procedure "%s" not found', $procedureId));
59
            throw new NotFoundHttpException(sprintf('Vetting procedure "%s" not found', $procedureId));
60
        }
61
62
        return $this->renderInitiateForm($procedureId, $this->getProvider($provider)->getName());
63
    }
64
65
    /**
66
     * @param string $procedureId
67
     * @param string $provider
68
     * @return array|Response
69
     */
70
    public function authenticateAction($procedureId, $provider)
71
    {
72
        $this->assertSecondFactorEnabled($provider);
73
74
        $this->denyAccessUnlessGranted(['ROLE_RA']);
75
76
        $logger = $this->get('ra.procedure_logger')->forProcedure($procedureId);
77
        $logger->notice('Generating GSSF verification request', ['provider' => $provider]);
78
79 View Code Duplication
        if (!$this->getVettingService()->hasProcedure($procedureId)) {
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...
80
            $logger->notice(sprintf('Vetting procedure "%s" not found', $procedureId));
81
            throw new NotFoundHttpException(sprintf('Vetting procedure "%s" not found', $procedureId));
82
        }
83
84
        $provider = $this->getProvider($provider);
85
86
        $authnRequest = AuthnRequestFactory::createNewRequest(
87
            $provider->getServiceProvider(),
88
            $provider->getRemoteIdentityProvider()
89
        );
90
91
        /** @var \Surfnet\StepupRa\RaBundle\Service\VettingService $vettingService */
92
        $vettingService = $this->get('ra.service.vetting');
93
        $authnRequest->setSubject($vettingService->getSecondFactorIdentifier($procedureId));
94
95
        $stateHandler = $provider->getStateHandler();
96
        $stateHandler->setRequestId($authnRequest->getRequestId());
97
98
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
99
        $redirectBinding = $this->get('surfnet_saml.http.redirect_binding');
100
101
        $logger->notice(
102
            sprintf(
103
                'Sending AuthnRequest with request ID: "%s" to GSSP "%s" at "%s"',
104
                $authnRequest->getRequestId(),
105
                $provider->getName(),
106
                $provider->getRemoteIdentityProvider()->getSsoUrl()
107
            ),
108
            ['provider' => $provider]
109
        );
110
111
        $vettingService->startGssfVerification($procedureId);
112
113
        return $redirectBinding->createRedirectResponseFor($authnRequest);
0 ignored issues
show
Deprecated Code introduced by
The method Surfnet\SamlBundle\Http\...teRedirectResponseFor() has been deprecated with message: Please use the `createResponseFor` method instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
114
    }
115
116
    /**
117
     * @param Request $httpRequest
118
     * @param string  $provider
119
     * @return array|Response
120
     */
121
    public function verifyAction(Request $httpRequest, $provider)
122
    {
123
        $this->assertSecondFactorEnabled($provider);
124
125
        $provider = $this->getProvider($provider);
126
127
        $this->get('logger')->notice(
128
            sprintf('Received GSSP "%s" SAMLResponse through Gateway, attempting to process', $provider->getName())
129
        );
130
131
        try {
132
            /** @var \Surfnet\SamlBundle\Http\PostBinding $postBinding */
133
            $postBinding = $this->get('surfnet_saml.http.post_binding');
134
            $assertion = $postBinding->processResponse(
135
                $httpRequest,
136
                $provider->getRemoteIdentityProvider(),
137
                $provider->getServiceProvider()
138
            );
139
        } catch (Exception $exception) {
140
            $provider->getStateHandler()->clear();
141
            $this->getLogger()->error(
142
                sprintf('Could not process received Response, error: "%s"', $exception->getMessage())
143
            );
144
145
            throw new BadRequestHttpException(
146
                'Could not process received SAML response, cannot return to vetting procedure'
147
            );
148
        }
149
150
        $expectedResponseTo = $provider->getStateHandler()->getRequestId();
151
        $provider->getStateHandler()->clear();
152
153
        if (!InResponseTo::assertEquals($assertion, $expectedResponseTo)) {
154
            $this->getLogger()->critical(sprintf(
155
                'Received Response with unexpected InResponseTo: %s',
156
                ($expectedResponseTo ? 'expected "' . $expectedResponseTo . '"' : ' no response expected')
157
            ));
158
159
            throw new BadRequestHttpException('Received unexpected SAML response, cannot return to vetting procedure');
160
        }
161
162
        $this->get('logger')->notice(
163
            sprintf('Processed GSSP "%s" SAMLResponse received through Gateway successfully', $provider->getName())
164
        );
165
166
        /** @var \Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary $attributeDictionary */
167
        $attributeDictionary = $this->get('surfnet_saml.saml.attribute_dictionary');
168
        $gssfId = $attributeDictionary->translate($assertion)->getNameID();
169
170
        /** @var \Surfnet\StepupRa\RaBundle\Service\VettingService $vettingService */
171
        $vettingService = $this->get('ra.service.vetting');
172
        $result = $vettingService->verifyGssfId($gssfId);
173
174
        if ($result->isSuccess()) {
175
            $this->getLogger()->notice('GSSP possession proven successfully');
176
177
            return $this->redirectToRoute('ra_vetting_verify_identity', ['procedureId' => $result->getProcedureId()]);
178
        }
179
180
        if (!$result->getProcedureId()) {
181
            // Should be unreachable statement, because the request ID is compared to the response ID a few lines before
182
            // this.
183
            throw new RuntimeException('Procedure ID for GSSF verification procedure could not be recovered.');
184
        }
185
186
        $this->getLogger()->notice(
187
            'Unable to prove possession of correct GSSF: ' .
188
            'GSSF ID registered in Self-Service does not match current GSSF ID'
189
        );
190
191
        return $this->renderInitiateForm(
192
            $result->getProcedureId(),
193
            $provider->getName(),
194
            ['gssfIdMismatch' => true]
195
        );
196
    }
197
198
    /**
199
     * @param string $provider
200
     * @return \Symfony\Component\HttpFoundation\Response
201
     */
202
    public function metadataAction($provider)
203
    {
204
        $this->assertSecondFactorEnabled($provider);
205
206
        $provider = $this->getProvider($provider);
207
208
        /** @var \Surfnet\SamlBundle\Metadata\MetadataFactory $factory */
209
        $factory = $this->get('gssp.provider.' . $provider->getName() . '.metadata.factory');
210
211
        return new XMLResponse($factory->generate());
212
    }
213
214
    /**
215
     * @param string $provider
216
     * @return \Surfnet\StepupRa\SamlStepupProviderBundle\Provider\Provider
217
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
218
     */
219
    private function getProvider($provider)
220
    {
221
        /** @var \Surfnet\StepupRa\SamlStepupProviderBundle\Provider\ProviderRepository $providerRepository */
222
        $providerRepository = $this->get('gssp.provider_repository');
223
224
        if (!$providerRepository->has($provider)) {
225
            $this->get('logger')->info(sprintf('Requested GSSP "%s" does not exist or is not registered', $provider));
226
227
            throw new NotFoundHttpException('Requested provider does not exist');
228
        }
229
230
        return $providerRepository->get($provider);
231
    }
232
233
    /**
234
     * @return \Psr\Log\LoggerInterface
235
     */
236
    private function getLogger()
237
    {
238
        return $this->get('logger');
239
    }
240
241
    /**
242
     * @return VettingService
243
     */
244
    private function getVettingService()
245
    {
246
        return $this->get('ra.service.vetting');
247
    }
248
249
    /**
250
     * @param string $procedureId
251
     * @param string $provider
252
     * @param array  $parameters
253
     * @return Response
254
     */
255
    private function renderInitiateForm($procedureId, $provider, array $parameters = [])
256
    {
257
        $collection = $this->get("surfnet_stepup.provider.collection");
258
        $secondFactorConfig = $collection->getByIdentifier($provider);
259
260
        $form = $this->createForm(
261
            InitiateGssfType::class,
262
            null,
263
            [
264
                'procedureId' => $procedureId,
265
                'provider' => $provider,
266
                /** @Ignore from translation message extraction */
267
                'label' => $secondFactorConfig->getInitiate()
268
            ]
269
        );
270
271
        $templateParameters = array_merge(
272
            $parameters,
273
            [
274
                'form' => $form->createView(),
275
                'procedureId' => $procedureId,
276
                'provider' => $provider,
277
                'secondFactorConfig' => $secondFactorConfig
278
            ]
279
        );
280
281
        return $this->render('SurfnetStepupRaRaBundle:vetting/gssf:initiate.html.twig', $templateParameters);
282
    }
283
}
284