Completed
Push — main ( d02cfb...38ec3a )
by
unknown
22s queued 15s
created

GsspFallbackService::createSecondFactor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
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
            $logger->info(
74
                sprintf(
75
                    'GSSP extension found, setting user attributes in state: subject: %s, institution: %s',
76
                    $subject,
77
                    $institution
78
                )
79
            );
80
81
            $this->stateHandler->setGsspUserAttributes($subject, $institution);
0 ignored issues
show
Bug introduced by
It seems like $institution can also be of type null; however, parameter $institution of Surfnet\StepupGateway\Ga...setGsspUserAttributes() 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

81
            $this->stateHandler->setGsspUserAttributes($subject, /** @scrutinizer ignore-type */ $institution);
Loading history...
Bug introduced by
It seems like $subject can also be of type null; however, parameter $subject of Surfnet\StepupGateway\Ga...setGsspUserAttributes() 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

81
            $this->stateHandler->setGsspUserAttributes(/** @scrutinizer ignore-type */ $subject, $institution);
Loading history...
82
        }
83
    }
84
85
    public function determineGsspFallbackNeeded(
86
        string $identityNameId,
87
        string $authenticationMode,
88
        Loa $requestedLoa,
89
        WhitelistService $whitelistService,
90
        LoggerInterface $logger,
91
        string $locale,
92
    ): bool {
93
94
        // Determine if the GSSP fallback flow should be started based on the following conditions:
95
        // - the authentication mode is SFO
96
        // - a fallback GSSP is configured
97
        // - a LoA1.5 (i.e. self asserted) authentication is requested
98
        // - the GSSP user attributes are available in the AuthnRequest
99
        // - the GSSP institution in the extension is whitelisted
100
        // - this "fallback" option is enabled for the institution that the user belongs to.
101
        // - the user has no registered tokens
102
103
        if ($authenticationMode !== SecondFactorController::MODE_SFO) {
104
            $this->stateHandler->setSecondFactorIsFallback(false);
105
            return false;
106
        }
107
108
        if (!$this->config->isConfigured()) {
109
            $this->stateHandler->setSecondFactorIsFallback(false);
110
            return false;
111
        }
112
113
        if (!$requestedLoa->levelIsLowerOrEqualTo(Loa::LOA_SELF_VETTED)) {
114
            $logger->info('Gssp Fallback configured but not used, requested LoA is higher than self-vetted');
115
            $this->stateHandler->setSecondFactorIsFallback(false);
116
            return false;
117
        }
118
119
        $subject = $this->stateHandler->getGsspUserAttributeSubject();
120
        $institution = $this->stateHandler->getGsspUserAttributeInstitution();
121
        if (empty($subject) || empty($institution)) {
122
            $this->stateHandler->setSecondFactorIsFallback(false);
123
            $logger->info('Gssp Fallback configured but not used, GSSP user attributes are not set in AuthnRequest');
124
            return false;
125
        }
126
127
        if (!$whitelistService->contains($institution)) {
128
            $this->stateHandler->setSecondFactorIsFallback(false);
129
            $logger->info('Gssp Fallback configured but not used, GSSP institution is not whitelisted');
130
            return false;
131
        }
132
133
        $institutionConfiguration = $this->institutionConfigurationRepository->getInstitutionConfiguration($institution);
134
        if (!$institutionConfiguration->ssoRegistrationBypass) {
135
            $this->stateHandler->setSecondFactorIsFallback(false);
136
            $logger->info('Gssp Fallback configured but not used, GSSP fallback is not enabled for the institution');
137
            return false;
138
        }
139
        
140
        if ($this->secondFactorRepository->hasTokens($identityNameId)) {
141
            $this->stateHandler->setSecondFactorIsFallback(false);
142
            $logger->info('Gssp Fallback configured but not used, the identity has registered tokens');
143
            return false;
144
        }
145
146
        $logger->info('Gssp Fallback flow started');
147
148
        $this->stateHandler->setSecondFactorIsFallback(true);
149
        $this->stateHandler->setPreferredLocale($locale);
150
151
        return true;
152
    }
153
154
    public function isSecondFactorFallback(): bool
155
    {
156
        return $this->stateHandler->isSecondFactorFallback();
157
    }
158
159
    public function createSecondFactor(): SecondFactorInterface
160
    {
161
        return SecondfactorGsspFallback::create(
162
            $this->stateHandler->getGsspUserAttributeSubject(),
163
            $this->stateHandler->getGsspUserAttributeInstitution(),
164
            $this->config->getGssp(),
165
            (string)$this->stateHandler->getPreferredLocale()
166
        );
167
    }
168
}
169