SamlController   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 127
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 60
dl 0
loc 127
rs 10
c 4
b 0
f 0
wmc 11

4 Methods

Rating   Name   Duplication   Size   Complexity  
A testSecondFactor() 0 32 3
A metadata() 0 8 1
B consumeAssertion() 0 59 6
A __construct() 0 14 1
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;
22
23
use Exception;
24
use Psr\Log\LoggerInterface;
25
use Surfnet\SamlBundle\Entity\IdentityProvider;
26
use Surfnet\SamlBundle\Entity\ServiceProvider;
27
use Surfnet\SamlBundle\Http\PostBinding;
28
use Surfnet\SamlBundle\Http\RedirectBinding;
29
use Surfnet\SamlBundle\Http\XMLResponse;
30
use Surfnet\SamlBundle\Metadata\MetadataFactory;
31
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
32
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
33
use Surfnet\StepupBundle\Service\LoaResolutionService;
34
use Surfnet\StepupBundle\Value\Loa;
35
use Surfnet\StepupSelfService\SelfServiceBundle\Controller\SelfVet\SelfVetController;
36
use Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\Session\SessionStorage;
37
use Surfnet\StepupSelfService\SelfServiceBundle\Service\SecondFactorService;
38
use Surfnet\StepupSelfService\SelfServiceBundle\Service\SelfAssertedTokens\RecoveryTokenState;
39
use Surfnet\StepupSelfService\SelfServiceBundle\Service\TestSecondFactor\TestAuthenticationRequestFactory;
0 ignored issues
show
Bug introduced by
The type Surfnet\StepupSelfServic...nticationRequestFactory 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...
40
use Surfnet\StepupSelfService\SelfServiceBundle\Value\SelfVetRequestId;
41
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
42
use Symfony\Component\HttpFoundation\RedirectResponse;
43
use Symfony\Component\HttpFoundation\Request;
44
use Symfony\Component\HttpFoundation\RequestStack;
45
use Symfony\Component\HttpFoundation\Response;
46
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
47
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
48
use Symfony\Component\Routing\Attribute\Route;
49
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
50
use Symfony\Component\Security\Core\Exception\AuthenticationException;
51
52
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
53
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) -- Hard to reduce due to different commands and queries used.
54
 * @SuppressWarnings(PHPMD.ExcessiveParameterList) -- Many fields/dependencies are required in controllers.
55
 * Previously they were yanked from the container via $this->get(),
56
 * implicit coupling has been made explicit with this change
57
 */
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...
58
class SamlController extends AbstractController
59
{
60
    public function __construct(
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
61
        private readonly LoggerInterface                  $logger,
62
        private readonly SecondFactorService              $secondFactorService,
63
        private readonly RequestStack                     $requestStack,
64
        private readonly LoaResolutionService             $loaResolutionService,
65
        private readonly MetadataFactory                  $metadataFactory,
66
        private readonly SamlAuthenticationLogger         $samlAuthenticationLogger,
67
        private readonly SessionStorage                   $authenticationStateHandler,
68
        private readonly TestAuthenticationRequestFactory $testAuthenticationRequestFactory,
69
        private readonly RedirectBinding                  $redirectBinding,
70
        private readonly PostBinding                      $postBinding,
71
        private readonly ServiceProvider                  $serviceProvider,
72
        private readonly IdentityProvider                 $testIdentityProvider,
73
    ) {
74
    }
75
76
    /**
77
     * A SelfService user is able to test it's token in this endpoint
78
     *
79
     * @throws NotFoundHttpException
80
     * @throws AccessDeniedException
81
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
82
    #[Route(path: '/second-factor/test', name: 'ss_second_factor_test', methods: ['GET'])]
83
    public function testSecondFactor(): RedirectResponse
84
    {
85
        $this->logger->notice('Starting second factor test');
86
87
        $identity = $this->getUser()->getIdentity();
0 ignored issues
show
Bug introduced by
The method getIdentity() does not exist on Symfony\Component\Security\Core\User\UserInterface. It seems like you code against a sub-type of Symfony\Component\Security\Core\User\UserInterface such as Surfnet\StepupSelfServic...n\AuthenticatedIdentity. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
        $identity = $this->getUser()->/** @scrutinizer ignore-call */ getIdentity();
Loading history...
88
89
        $vettedSecondFactors = $this->secondFactorService->findVettedByIdentity($identity->id);
90
        if (!$vettedSecondFactors || $vettedSecondFactors->getTotalItems() === 0) {
91
            $this->logger->error(
92
                sprintf(
93
                    'Identity "%s" tried to test a second factor, but does not own a suitable vetted token.',
94
                    $identity->id,
95
                ),
96
            );
97
98
            throw new NotFoundHttpException();
99
        }
100
101
102
        // By requesting LoA 1.5 any relevant token can be tested (LoA 2 and 3)
103
        $authenticationRequest = $this->testAuthenticationRequestFactory->createSecondFactorTestRequest(
104
            $identity->nameId,
105
            $this->loaResolutionService->getLoaByLevel(Loa::LOA_SELF_VETTED),
106
        );
107
108
        $this->authenticationStateHandler->setRequestId($authenticationRequest->getRequestId());
109
110
        $samlLogger = $this->samlAuthenticationLogger->forAuthentication($authenticationRequest->getRequestId());
111
        $samlLogger->notice('Sending authentication request to the second factor test IDP');
112
113
        return $this->redirectBinding->createResponseFor($authenticationRequest);
114
    }
115
116
    #[Route(
117
        path: '/authentication/consume-assertion',
118
        name: 'selfservice_serviceprovider_consume_assertion',
119
        methods: ['POST'],
120
    )]
121
    public function consumeAssertion(Request $httpRequest): Response
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function consumeAssertion()
Loading history...
122
    {
123
        $session = $this->requestStack->getSession();
124
        if ($session->has(SelfVetController::SELF_VET_SESSION_ID)) {
125
            // The test authentication IdP is also used for self vetting, a different session id is
126
            // used to mark a self vet command
127
            /** @var SelfVetRequestId $selfVetRequestId */
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...
128
            $selfVetRequestId = $session->get(SelfVetController::SELF_VET_SESSION_ID);
129
            $secondFactorId = $selfVetRequestId->vettingSecondFactorId();
130
            return $this->forward(
131
                'Surfnet\StepupSelfService\SelfServiceBundle\Controller\SelfVet\SelfVetConsumeController::consumeSelfVetAssertion',
132
                ['secondFactorId' => $secondFactorId],
133
            );
134
        }
135
        if ($session->has(RecoveryTokenState::RECOVERY_TOKEN_STEP_UP_REQUEST_ID_IDENTIFIER)) {
136
            // The test authentication IdP is also used for self-asserted recovery token
137
            // verification a different session id is used to mark the authentication.
138
            return $this->forward('Surfnet\StepupSelfService\SelfServiceBundle\Controller\RecoveryTokenController::stepUpConsumeAssertion');
139
        }
140
        if (!$this->authenticationStateHandler->hasRequestId()) {
141
            $this->logger->error(
142
                'Received an authentication response for testing a second factor, but no second factor test response was expected',
143
            );
144
145
            throw new AccessDeniedHttpException('Did not expect an authentication response');
146
        }
147
        $this->logger->notice('Received an authentication response for testing a second factor');
148
        $initiatedRequestId = $this->authenticationStateHandler->getRequestId();
149
        $samlLogger = $this->samlAuthenticationLogger->forAuthentication($initiatedRequestId);
0 ignored issues
show
Bug introduced by
It seems like $initiatedRequestId can also be of type null; however, parameter $requestId of Surfnet\SamlBundle\Monol...er::forAuthentication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        $samlLogger = $this->samlAuthenticationLogger->forAuthentication(/** @scrutinizer ignore-type */ $initiatedRequestId);
Loading history...
150
        $this->authenticationStateHandler->clearRequestId();
151
        try {
152
            $assertion = $this->postBinding->processResponse(
153
                $httpRequest,
154
                $this->testIdentityProvider,
155
                $this->serviceProvider,
156
            );
157
158
            if (!InResponseTo::assertEquals($assertion, $initiatedRequestId)) {
159
                $samlLogger->error(
160
                    sprintf(
161
                        'Expected a response to the request with ID "%s", but the SAMLResponse was a response to a different request',
162
                        $initiatedRequestId,
163
                    ),
164
                );
165
166
                throw new AuthenticationException('Unexpected InResponseTo in SAMLResponse');
167
            }
168
169
            $this->addFlash('success', 'ss.test_second_factor.verification_successful');
170
        } catch (Exception $e) {
171
            $this->logger->info('Receiving the test second factor assertion failed, see context for details', ['context' => $e->getMessage()]);
172
            $this->addFlash('error', 'ss.test_second_factor.verification_failed');
173
        }
174
        return $this->redirectToRoute('ss_second_factor_list');
175
    }
176
177
    #[Route(
178
        path: '/authentication/metadata',
179
        name: 'selfservice_saml_metadata',
180
        methods: ['GET'],
181
    )]
182
    public function metadata(): XMLResponse
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function metadata()
Loading history...
183
    {
184
        return new XMLResponse($this->metadataFactory->generate()->__toString());
185
    }
186
}
187