GsspFallbackService::determineGsspFallbackNeeded()   B
last analyzed

Complexity

Conditions 10
Paths 9

Size

Total Lines 74
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 38
c 1
b 0
f 0
nc 9
nop 6
dl 0
loc 74
rs 7.6666

How to fix   Long Method    Complexity   

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 2025 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\StepupGateway\SecondFactorOnlyBundle\Service\Gateway;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\SamlBundle\SAML2\ReceivedAuthnRequest;
23
use Surfnet\StepupBundle\Value\Loa;
24
use Surfnet\StepupGateway\GatewayBundle\Controller\SecondFactorController;
25
use Surfnet\StepupGateway\GatewayBundle\Entity\InstitutionConfigurationRepository;
26
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactorRepository;
27
use Surfnet\StepupGateway\GatewayBundle\Exception\InstitutionConfigurationNotFoundException;
28
use Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler;
29
use Surfnet\StepupGateway\GatewayBundle\Service\SecondFactor\SecondFactorInterface;
30
use Surfnet\StepupGateway\GatewayBundle\Service\WhitelistService;
31
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\Gateway\GsspFallback\GsspFallbackConfig;
32
33
class GsspFallbackService
34
{
35
36
    private SecondFactorRepository $secondFactorRepository;
37
    private InstitutionConfigurationRepository $institutionConfigurationRepository;
38
    private ProxyStateHandler $stateHandler;
39
    private GsspFallbackConfig $config;
40
41
    public function __construct(
42
        SecondFactorRepository $secondFactorRepository,
43
        InstitutionConfigurationRepository $institutionConfigurationRepository,
44
        ProxyStateHandler $stateHandler,
45
        GsspFallbackConfig $config,
46
    ) {
47
        $this->secondFactorRepository = $secondFactorRepository;
48
        $this->institutionConfigurationRepository = $institutionConfigurationRepository;
49
        $this->stateHandler = $stateHandler;
50
        $this->config = $config;
51
    }
52
53
    /**
54
     * @param ReceivedAuthnRequest $originalRequest
55
     */
56
    public function handleSamlGsspExtension(LoggerInterface $logger, ReceivedAuthnRequest $originalRequest): void
57
    {
58
        if (!$this->config->isConfigured()) {
59
            return;
60
        }
61
62
        $logger->info('GSSP fallback configured, parsing GSSP extension from AuthnRequest');
63
64
        if ($originalRequest->getExtensions()->hasGsspUserAttributesChunk()) {
65
            $logger->info(
66
                sprintf('GSSP extension found, setting user attributes in state')
67
            );
68
69
            $gsspUserAttributes = $originalRequest->getExtensions()->getGsspUserAttributesChunk();
70
71
            $subject = $gsspUserAttributes->getAttributeValue($this->config->getSubjectAttribute());
72
            $institution = $gsspUserAttributes->getAttributeValue($this->config->getInstitutionAttribute());
73
74
            if (!is_string($subject) || !is_string($institution)) {
75
                $logger->notice('GSSP extension found, but subject and/or institution user attribute(s) are not set');
76
                return; // Only set if both attributes are present
77
            }
78
79
            $logger->info(
80
                sprintf(
81
                    'GSSP extension found, setting user attributes in state: subject: %s, institution: %s',
82
                    $subject,
83
                    $institution
84
                )
85
            );
86
87
            $this->stateHandler->setGsspUserAttributes($subject, $institution);
88
        }
89
    }
90
91
    /**
92
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
93
     * @SuppressWarnings(PHPMD.NPathComplexity)
94
     */
95
    public function determineGsspFallbackNeeded(
96
        string $identityNameId,
97
        string $authenticationMode,
98
        Loa $requestedLoa,
99
        WhitelistService $whitelistService,
100
        LoggerInterface $logger,
101
        string $locale,
102
    ): bool {
103
104
        // Determine if the GSSP fallback flow should be started based on the following conditions:
105
        // - the authentication mode is SFO
106
        // - a fallback GSSP is configured
107
        // - a LoA1.5 (i.e. self asserted) authentication is requested
108
        // - the GSSP user attributes are available in the AuthnRequest
109
        // - the GSSP institution in the extension is whitelisted
110
        // - this "fallback" option is enabled for the institution that the user belongs to.
111
        // - the user has no registered tokens
112
113
        if ($authenticationMode !== SecondFactorController::MODE_SFO) {
114
            $this->stateHandler->setSecondFactorIsFallback(false);
115
            return false;
116
        }
117
118
        if (!$this->config->isConfigured()) {
119
            $this->stateHandler->setSecondFactorIsFallback(false);
120
            return false;
121
        }
122
123
        if (!$requestedLoa->levelIsLowerOrEqualTo(Loa::LOA_SELF_VETTED)) {
124
            $logger->info('Gssp Fallback configured but not used, requested LoA is higher than self-vetted');
125
            $this->stateHandler->setSecondFactorIsFallback(false);
126
            return false;
127
        }
128
129
        $subject = $this->stateHandler->getGsspUserAttributeSubject();
130
        $institution = $this->stateHandler->getGsspUserAttributeInstitution();
131
        if (empty($subject) || empty($institution)) {
132
            $this->stateHandler->setSecondFactorIsFallback(false);
133
            $logger->info('Gssp Fallback configured but not used, GSSP user attributes are not set in AuthnRequest');
134
            return false;
135
        }
136
137
        if (!$whitelistService->contains($institution)) {
138
            $this->stateHandler->setSecondFactorIsFallback(false);
139
            $logger->info('Gssp Fallback configured but not used, GSSP institution is not whitelisted');
140
            return false;
141
        }
142
143
        try {
144
            $institutionConfiguration = $this->institutionConfigurationRepository->getInstitutionConfiguration($institution);
145
        } catch (InstitutionConfigurationNotFoundException) {
146
            $this->stateHandler->setSecondFactorIsFallback(false);
147
            $logger->info('Gssp Fallback configured but not used, GSSP institution configuration is not found');
148
            return false;
149
        }
150
151
        if (!$institutionConfiguration->ssoRegistrationBypass) {
152
            $this->stateHandler->setSecondFactorIsFallback(false);
153
            $logger->info('Gssp Fallback configured but not used, GSSP fallback is not enabled for the institution');
154
            return false;
155
        }
156
        
157
        if ($this->secondFactorRepository->hasTokens($identityNameId)) {
158
            $this->stateHandler->setSecondFactorIsFallback(false);
159
            $logger->info('Gssp Fallback configured but not used, the identity has registered tokens');
160
            return false;
161
        }
162
163
        $logger->info('Gssp Fallback flow started');
164
165
        $this->stateHandler->setSecondFactorIsFallback(true);
166
        $this->stateHandler->setPreferredLocale($locale);
167
168
        return true;
169
    }
170
171
    public function isSecondFactorFallback(): bool
172
    {
173
        return $this->stateHandler->isSecondFactorFallback();
174
    }
175
176
    public function createSecondFactor(): SecondFactorInterface
177
    {
178
        return SecondfactorGsspFallback::create(
179
            $this->stateHandler->getGsspUserAttributeSubject(),
180
            $this->stateHandler->getGsspUserAttributeInstitution(),
181
            $this->config->getGssp(),
182
            (string)$this->stateHandler->getPreferredLocale()
183
        );
184
    }
185
}
186