Passed
Pull Request — main (#308)
by Michiel
14:02 queued 06:58
created

GssfController::renderStatusForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 15
c 3
b 0
f 0
nc 1
nop 2
dl 0
loc 28
rs 9.7666
1
<?php
2
3
declare(strict_types = 1);
4
5
/**
6
 * Copyright 2014 SURFnet bv
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
Coding Style introduced by
Missing @link tag in file comment
Loading history...
20
21
namespace Surfnet\StepupSelfService\SelfServiceBundle\Controller\Registration;
22
23
use Exception;
24
use JMS\TranslationBundle\Annotation\Ignore;
25
use Psr\Log\LoggerInterface;
26
use Surfnet\SamlBundle\Http\XMLResponse;
27
use Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary;
28
use Surfnet\SamlBundle\SAML2\AuthnRequestFactory;
29
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
30
use Surfnet\StepupBundle\Value\Provider\ViewConfigCollection;
31
use Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\MetadataFactoryCollection;
32
use Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\Provider;
33
use Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\ProviderRepository;
34
use Surfnet\StepupSelfService\SamlStepupProviderBundle\Provider\ViewConfig;
35
use Surfnet\StepupSelfService\SelfServiceBundle\Controller\Controller;
36
use Surfnet\StepupSelfService\SelfServiceBundle\Form\Type\StatusGssfType;
37
use Surfnet\StepupSelfService\SelfServiceBundle\Service\GssfService;
0 ignored issues
show
Bug introduced by
The type Surfnet\StepupSelfServic...dle\Service\GssfService was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
use Surfnet\StepupSelfService\SelfServiceBundle\Service\GsspUserAttributeService;
0 ignored issues
show
Bug introduced by
The type Surfnet\StepupSelfServic...sspUserAttributeService was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
use Surfnet\StepupSelfService\SelfServiceBundle\Service\InstitutionConfigurationOptionsService;
40
use Symfony\Component\HttpFoundation\RedirectResponse;
41
use Symfony\Component\HttpFoundation\Request;
42
use Symfony\Component\HttpFoundation\Response;
43
use Symfony\Component\Routing\Attribute\Route;
44
use Surfnet\SamlBundle\Http\RedirectBinding;
45
use \Surfnet\SamlBundle\Http\PostBinding;
0 ignored issues
show
Bug introduced by
The type \Surfnet\SamlBundle\Http\PostBinding was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
46
47
/**
48
 * Controls registration with Generic SAML Stepup Providers (GSSPs), yielding Generic SAML Second Factors (GSSFs).
49
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
50
final class GssfController extends Controller
51
{
52
    public function __construct(
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
53
        private readonly LoggerInterface           $logger,
54
        InstitutionConfigurationOptionsService     $configurationOptionsService,
55
        private readonly ProviderRepository        $providerRepository,
56
        private readonly RedirectBinding           $redirectBinding,
57
        private readonly PostBinding               $postBinding,
58
        private readonly GsspUserAttributeService  $gsspUserAttributeService,
59
        private readonly GssfService               $gssfService,
60
        private readonly AttributeDictionary       $attributeDictionary,
61
        private readonly MetadataFactoryCollection $metadataFactoryCollection,
62
        private readonly ViewConfigCollection      $viewConfigCollection
63
    ) {
64
        parent::__construct($logger, $configurationOptionsService);
65
    }
66
67
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $request should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $provider should have a doc-comment as per coding-style.
Loading history...
68
     * Render the status form.
69
     *
70
     * This action has two parameters:
71
     *
72
     * - authenticationFailed (default false), will trigger an error message
73
     *   and is used when a SAML failure response was received, for example
74
     *   when the users cancelled the registration
75
     *
76
     * - proofOfPossessionFailed (default false), will trigger an error message
77
     *   when possession was not proven, but the SAML response was successful
78
     */
0 ignored issues
show
Coding Style introduced by
There must be no blank lines after the function comment
Loading history...
Coding Style introduced by
Missing @return tag in function comment
Loading history...
79
    #[Route(
80
        path: '/registration/gssf/{provider}/status',
81
        name: 'ss_registration_gssf_status_report',
82
        defaults: ['authenticationFailed' => false, 'proofOfPossessionFailed'=> false ],
83
        methods: ['GET'],
84
    )]
85
86
    public function status(Request $request, string $provider): Response
87
    {
88
        $this->assertSecondFactorEnabled($provider);
89
90
        return $this->renderStatusForm(
91
            $provider,
92
            [
93
                'authenticationFailed' => (bool) $request->query->get('authenticationFailed'),
94
                'proofOfPossessionFailed' => (bool) $request->query->get('proofOfPossessionFailed'),
95
            ]
96
        );
97
    }
98
99
    #[Route(
100
        path: '/registration/gssf/{provider}/authenticate',
101
        name: 'ss_registration_gssf_authenticate',
102
        methods: ['POST'],
103
    )]
104
    public function authenticate(string $provider): Response
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function authenticate()
Loading history...
105
    {
106
        $this->assertSecondFactorEnabled($provider);
107
108
        $provider = $this->getProvider($provider);
109
110
        $authnRequest = AuthnRequestFactory::createNewRequest(
111
            $provider->getServiceProvider(),
112
            $provider->getRemoteIdentityProvider()
113
        );
114
115
        $this->gsspUserAttributeService->addGsspUserAttributes(
116
            $authnRequest,
117
            $provider,
118
            $this->getIdentity()
119
        );
120
        $stateHandler = $provider->getStateHandler();
121
        $stateHandler->setRequestId($authnRequest->getRequestId());
122
123
        $this->logger->notice(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
124
            'Sending AuthnRequest with request ID: "%s" to GSSP "%s" at "%s"',
125
            $authnRequest->getRequestId(),
126
            $provider->getName(),
127
            $provider->getRemoteIdentityProvider()->getSsoUrl()
128
        ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
129
130
        return $this->redirectBinding->createResponseFor($authnRequest);
131
    }
132
133
    #[Route(
134
        path: '/registration/gssf/{provider}/consume-assertion',
135
        name: 'ss_registration_gssf_consume_assertion',
136
        methods: ['POST'],
137
    )]
138
    public function consumeAssertion(Request $httpRequest, string $provider): array|Response
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function consumeAssertion()
Loading history...
139
    {
140
        $this->assertSecondFactorEnabled($provider);
141
142
        $provider = $this->getProvider($provider);
143
144
        $this->logger->notice(
145
            sprintf('Received GSSP "%s" SAMLResponse through Gateway, attempting to process', $provider->getName())
146
        );
147
148
        try {
149
            $assertion = $this->postBinding->processResponse(
150
                $httpRequest,
151
                $provider->getRemoteIdentityProvider(),
152
                $provider->getServiceProvider()
153
            );
154
        } catch (Exception $exception) {
155
            $provider->getStateHandler()->clear();
156
157
            $this->logger->error(
158
                sprintf('Could not process received Response, error: "%s"', $exception->getMessage())
159
            );
160
161
            return $this->redirectToStatusReportForm(
162
                $provider,
163
                ['authenticationFailed' => true]
164
            );
165
        }
166
167
        $expectedResponseTo = $provider->getStateHandler()->getRequestId();
168
        $provider->getStateHandler()->clear();
169
170
        if (!InResponseTo::assertEquals($assertion, $expectedResponseTo)) {
171
            $this->logger->critical(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
172
                'Received Response with unexpected InResponseTo, %s',
173
                ($expectedResponseTo ? 'expected "' . $expectedResponseTo . '"' : ' no response expected')
174
            ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
175
176
            return $this->redirectToStatusReportForm(
177
                $provider,
178
                ['authenticationFailed' => true]
179
            );
180
        }
181
182
        $this->logger->notice(
183
            sprintf('Processed GSSP "%s" SAMLResponse received through Gateway successfully', $provider->getName())
184
        );
185
186
        $gssfId = $this->attributeDictionary->translate($assertion)->getNameID();
187
188
        $secondFactorId = $this->gssfService->provePossession($this->getIdentity()->id, $provider->getName(), $gssfId);
189
190
        if ($secondFactorId) {
191
            $this->logger->notice('GSSF possession has been proven successfully');
192
193
            if ($this->emailVerificationIsRequired()) {
194
                return $this->redirectToRoute(
195
                    'ss_registration_email_verification_email_sent',
196
                    ['secondFactorId' => $secondFactorId]
197
                );
198
            } else {
199
                return $this->redirectToRoute(
200
                    'ss_second_factor_vetting_types',
201
                    ['secondFactorId' => $secondFactorId]
202
                );
203
            }
204
        }
205
206
        $this->logger->error('Unable to prove GSSF possession');
207
208
        return $this->redirectToStatusReportForm(
209
            $provider,
210
            ['proofOfPossessionFailed' => true]
211
        );
212
    }
213
214
    private function redirectToStatusReportForm(Provider $provider, array $options): RedirectResponse
0 ignored issues
show
Coding Style introduced by
Private method name "GssfController::redirectToStatusReportForm" must be prefixed with an underscore
Loading history...
Coding Style introduced by
Missing doc comment for function redirectToStatusReportForm()
Loading history...
215
    {
216
        return $this->redirectToRoute(
217
            'ss_registration_gssf_status_report',
218
            $options + [
219
                'provider' => $provider->getName(),
220
            ]
221
        );
222
    }
223
224
    #[Route(
225
        path: '/registration/gssf/{provider}/metadata',
226
        name: 'ss_registration_gssf_saml_metadata',
227
        methods: ['GET'],
228
    )]
229
    public function metadata(string $provider): XMLResponse
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function metadata()
Loading history...
230
    {
231
        $this->assertSecondFactorEnabled($provider);
232
233
        $provider = $this->getProvider($provider);
234
        $factory = $this->metadataFactoryCollection->getByIdentifier($provider->getName());
235
236
        return new XMLResponse($factory->generate()->__toString());
237
    }
238
239
    private function getProvider(string $provider): Provider
0 ignored issues
show
Coding Style introduced by
Private method name "GssfController::getProvider" must be prefixed with an underscore
Loading history...
Coding Style introduced by
Missing doc comment for function getProvider()
Loading history...
240
    {
241
        return $this->providerRepository->get($provider);
242
    }
243
244
    private function renderStatusForm(string $provider, array $parameters = []): Response
0 ignored issues
show
Coding Style introduced by
Private method name "GssfController::renderStatusForm" must be prefixed with an underscore
Loading history...
Coding Style introduced by
Missing doc comment for function renderStatusForm()
Loading history...
245
    {
246
        /** @var ViewConfig $secondFactorConfig */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
247
        $secondFactorConfig = $this->viewConfigCollection->getByIdentifier($provider);
248
249
        $form = $this->createForm(
250
            StatusGssfType::class,
251
            null,
252
            [
253
                'provider' => $provider,
254
                /** @Ignore from translation message extraction */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
255
                'label' => $secondFactorConfig->getInitiateButton()
256
            ]
257
        );
258
        $templateParameters = array_merge(
259
            $parameters,
260
            [
261
                'form' => $form->createView(),
262
                // TODO: createView() is deprecated?
263
                // see https://symfony.com/doc/6.4/forms.html#rendering-forms
264
                'provider' => $provider,
265
                'secondFactorConfig' => $secondFactorConfig,
266
                'verifyEmail' => $this->emailVerificationIsRequired(),
267
            ]
268
        );
269
        return $this->render(
270
            'registration/gssf/status.html.twig',
271
            $templateParameters
272
        );
273
    }
274
}
275