GsspFallbackService::determineGsspFallbackNeeded()   B
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 67
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 33
c 1
b 0
f 0
nc 8
nop 6
dl 0
loc 67
rs 8.0555

How to fix   Long Method   

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