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