Completed
Push — develop ( 9bc470...44ec0d )
by Michiel
06:03
created

Controller/Registration/GssfController.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\StepupSelfService\SelfServiceBundle\Controller\Registration;
20
21
use Exception;
22
use Surfnet\SamlBundle\Http\XMLResponse;
23
use Surfnet\SamlBundle\SAML2\AuthnRequestFactory;
24
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
25
use Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\Provider;
26
use Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\ViewConfig;
27
use Surfnet\StepupSelfService\SelfServiceBundle\Controller\Controller;
28
use Symfony\Component\HttpFoundation\Request;
29
use Symfony\Component\HttpFoundation\Response;
30
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
31
32
/**
33
 * Controls registration with Generic SAML Stepup Providers (GSSPs), yielding Generic SAML Second Factors (GSSFs).
34
 */
35
final class GssfController extends Controller
36
{
37
    /**
38
     * Render the initiation form.
39
     *
40
     * This action has two parameters:
41
     *
42
     * - authenticationFailed (default false), will trigger an error message
43
     *   and is used when a SAML failure response was received, for example
44
     *   when the users cancelled the registration
45
     *
46
     * - proofOfPossessionFailed (default false), will trigger an error message
47
     *   when possession was not proven, but the SAML response was successful
48
     *
49
     * @param Request $request
50
     * @param string $provider
51
     * @return array|Response
52
     */
53
    public function initiateAction(Request $request, $provider)
54
    {
55
        $this->assertSecondFactorEnabled($provider);
56
57
        return $this->renderInitiateForm(
58
            $provider,
59
            [
60
                'authenticationFailed' => (bool) $request->get('authenticationFailed'),
61
                'proofOfPossessionFailed' => (bool) $request->get('proofOfPossessionFailed'),
62
            ]
63
        );
64
    }
65
66
    /**
67
     * @param string $provider
68
     * @return array|Response
69
     */
70
    public function authenticateAction($provider)
71
    {
72
        $this->assertSecondFactorEnabled($provider);
73
74
        $provider = $this->getProvider($provider);
75
76
        $authnRequest = AuthnRequestFactory::createNewRequest(
77
            $provider->getServiceProvider(),
78
            $provider->getRemoteIdentityProvider()
79
        );
80
81
        $stateHandler = $provider->getStateHandler();
82
        $stateHandler->setRequestId($authnRequest->getRequestId());
83
84
        /** @var \Surfnet\SamlBundle\Http\RedirectBinding $redirectBinding */
85
        $redirectBinding = $this->get('surfnet_saml.http.redirect_binding');
86
87
        $this->getLogger()->notice(sprintf(
88
            'Sending AuthnRequest with request ID: "%s" to GSSP "%s" at "%s"',
89
            $authnRequest->getRequestId(),
90
            $provider->getName(),
91
            $provider->getRemoteIdentityProvider()->getSsoUrl()
92
        ));
93
94
        return $redirectBinding->createRedirectResponseFor($authnRequest);
95
    }
96
97
    /**
98
     * @param Request $httpRequest
99
     * @param string  $provider
100
     * @return array|Response
101
     */
102
    public function consumeAssertionAction(Request $httpRequest, $provider)
103
    {
104
        $this->assertSecondFactorEnabled($provider);
105
106
        $provider = $this->getProvider($provider);
107
108
        $this->get('logger')->notice(
109
            sprintf('Received GSSP "%s" SAMLResponse through Gateway, attempting to process', $provider->getName())
110
        );
111
112
        try {
113
            /** @var \Surfnet\SamlBundle\Http\PostBinding $postBinding */
114
            $postBinding = $this->get('surfnet_saml.http.post_binding');
115
            $assertion = $postBinding->processResponse(
116
                $httpRequest,
117
                $provider->getRemoteIdentityProvider(),
118
                $provider->getServiceProvider()
119
            );
120
        } catch (Exception $exception) {
121
            $provider->getStateHandler()->clear();
122
123
            $this->getLogger()->error(
124
                sprintf('Could not process received Response, error: "%s"', $exception->getMessage())
125
            );
126
127
            return $this->redirectToInitiationForm(
128
                $provider,
129
                ['authenticationFailed' => true]
130
            );
131
        }
132
133
        $expectedResponseTo = $provider->getStateHandler()->getRequestId();
134
        $provider->getStateHandler()->clear();
135
136
        if (!InResponseTo::assertEquals($assertion, $expectedResponseTo)) {
137
            $this->getLogger()->critical(sprintf(
138
                'Received Response with unexpected InResponseTo, %s',
139
                ($expectedResponseTo ? 'expected "' . $expectedResponseTo . '"' : ' no response expected')
140
            ));
141
142
            return $this->redirectToInitiationForm(
143
                $provider,
144
                ['authenticationFailed' => true]
145
            );
146
        }
147
148
        $this->get('logger')->notice(
149
            sprintf('Processed GSSP "%s" SAMLResponse received through Gateway successfully', $provider->getName())
150
        );
151
152
        /** @var \Surfnet\StepupSelfService\SelfServiceBundle\Service\GssfService $service */
153
        $service = $this->get('surfnet_stepup_self_service_self_service.service.gssf');
154
        /** @var \Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary $attributeDictionary */
155
        $attributeDictionary = $this->get('surfnet_saml.saml.attribute_dictionary');
156
        $gssfId = $attributeDictionary->translate($assertion)->getNameID();
157
158
        $secondFactorId = $service->provePossession($this->getIdentity()->id, $provider->getName(), $gssfId);
159
160 View Code Duplication
        if ($secondFactorId) {
0 ignored issues
show
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...
161
            $this->getLogger()->notice('GSSF possession has been proven successfully');
162
163
            if ($this->emailVerificationIsRequired()) {
164
                return $this->redirectToRoute(
165
                    'ss_registration_email_verification_email_sent',
166
                    ['secondFactorId' => $secondFactorId]
167
                );
168
            } else {
169
                return $this->redirectToRoute(
170
                    'ss_registration_registration_email_sent',
171
                    ['secondFactorId' => $secondFactorId]
172
                );
173
            }
174
        }
175
176
        $this->getLogger()->error('Unable to prove GSSF possession');
177
178
        return $this->redirectToInitiationForm(
179
            $provider,
180
            ['proofOfPossessionFailed' => true]
181
        );
182
    }
183
184
    /**
185
     * @param Provider $provider
186
     * @param array $options
187
     */
188
    private function redirectToInitiationForm(Provider $provider, array $options)
189
    {
190
        return $this->redirectToRoute(
191
            'ss_registration_gssf_initiate',
192
            $options + [
193
                'provider' => $provider->getName(),
194
            ]
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\StepupSelfService\SamlStepupProviderBundle\Provider\Provider
217
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
218
     */
219
    private function getProvider($provider)
220
    {
221
        /** @var \Surfnet\StepupSelfService\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
     * @param string $provider
243
     * @param array $parameters
244
     * @return Response
245
     */
246
    private function renderInitiateForm($provider, array $parameters = [])
247
    {
248
        /** @var ViewConfig $secondFactorConfig */
249
        $secondFactorConfig = $this->get("gssp.view_config.{$provider}");
250
251
        $form = $this->createForm(
252
            'ss_initiate_gssf',
253
            null,
254
            [
255
                'provider' => $provider,
256
                /** @Ignore from translation message extraction */
257
                'label' => $secondFactorConfig->getInitiateButton()
258
            ]
259
        );
260
        /** @var ViewConfig $secondFactorConfig */
261
        $templateParameters = array_merge(
262
            $parameters,
263
            [
264
                'form' => $form->createView(),
265
                'provider' => $provider,
266
                'secondFactorConfig' => $secondFactorConfig,
267
                'verifyEmail' => $this->emailVerificationIsRequired(),
268
            ]
269
        );
270
        return $this->render(
271
            'SurfnetStepupSelfServiceSelfServiceBundle:Registration/Gssf:initiate.html.twig',
272
            $templateParameters
273
        );
274
    }
275
}
276