Passed
Pull Request — main (#336)
by
unknown
07:56
created

ActivationFlowService::getFlowPreferenceFromUri()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 43
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 26
c 0
b 0
f 0
nc 8
nop 1
dl 0
loc 43
rs 9.1928
1
<?php
2
3
declare(strict_types = 1);
4
5
/**
6
 * Copyright 2022 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\Service;
22
23
use Psr\Log\LoggerInterface;
24
use Surfnet\StepupSelfService\SelfServiceBundle\Exception\LogicException;
25
use Surfnet\StepupSelfService\SelfServiceBundle\Value\ActivationFlowPreference;
26
use Surfnet\StepupSelfService\SelfServiceBundle\Value\ActivationFlowPreferenceInterface;
27
use Surfnet\StepupSelfService\SelfServiceBundle\Value\ActivationFlowPreferenceNotExpressed;
28
use Symfony\Component\HttpFoundation\RequestStack;
29
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
30
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
31
32
class ActivationFlowService
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class ActivationFlowService
Loading history...
33
{
34
    private const ACTIVATION_FLOW_PREFERENCE_SESSION_NAME = 'self_service_activation_flow_preference';
35
    private const ACTIVATION_FLOW_ENTITLEMENT_SAML_ATTRIBUTE = 'urn:mace:dir:attribute-def:eduPersonEntitlement';
36
37
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $requestStack should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $logger should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $fieldName should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $tokenStorage should have a doc-comment as per coding-style.
Loading history...
38
     * Handle preferred activation flow logic
39
     *
40
     * 1. On the / path, the preferred activation flow can be set using the configured
41
     *    query string field. This field can have an option indicating the preferred flow.
42
     *    This is stored in session.
43
     * 2. If this preference was set, the list of available options is limited to just that
44
     *    option.
45
     * 3. The option can be reset by the Identity, removing it from session.
46
     *
47
     * Note that:
48
     * - fieldName and options are configured in the SelfServiceExtension
49
     *
50
     * @param string[] $options
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Doc comment for parameter $options does not match actual variable name $requestStack
Loading history...
51
     * @param array<string, string> $attributes
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter $attributes does not match actual variable name $tokenStorage
Loading history...
52
     */
53
    public function __construct(
54
        private readonly RequestStack $requestStack,
55
        private readonly TokenStorageInterface $tokenStorage,
56
        private readonly LoggerInterface $logger,
57
        private readonly string $fieldName,
58
        private readonly array $options,
59
        private readonly array $attributes
60
    ) {
61
    }
62
63
    public function process(string $uri): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function process()
Loading history...
64
    {
65
        $requestedActivationPreference = $this->getFlowPreferenceFromUri($uri);
66
        if ($requestedActivationPreference instanceof ActivationFlowPreferenceNotExpressed) {
67
            return;
68
        }
69
70
        $availableActivationPreferences = $this->getFlowPreferencesFromSamlAttributes();
71
72
        if (count($availableActivationPreferences) == 0) {
73
            $this->logger->info('No entitlement attributes found to determine the allowed flow, allowing all flows');
74
            $availableActivationPreferences = [
75
                'ra', 'self'
76
            ];
77
        }
78
79
        if (in_array($requestedActivationPreference, $availableActivationPreferences)) {
80
            $this->logger->info('Storing the preference in session');
81
            $this->requestStack->getSession()->set(
82
                self::ACTIVATION_FLOW_PREFERENCE_SESSION_NAME,
83
                $requestedActivationPreference
84
            );
85
        }
86
    }
87
88
    public function hasActivationFlowPreference(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function hasActivationFlowPreference()
Loading history...
89
    {
90
        return $this->requestStack->getSession()->has(self::ACTIVATION_FLOW_PREFERENCE_SESSION_NAME);
91
    }
92
93
    public function getPreference(): ActivationFlowPreferenceInterface
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getPreference()
Loading history...
94
    {
95
        if (!$this->hasActivationFlowPreference()) {
96
            return new ActivationFlowPreferenceNotExpressed();
97
        }
98
        return $this->requestStack->getSession()->get(self::ACTIVATION_FLOW_PREFERENCE_SESSION_NAME);
99
    }
100
101
    private function getFlowPreferenceFromUri(string $uri): ActivationFlowPreferenceInterface
0 ignored issues
show
Coding Style introduced by
Private method name "ActivationFlowService::getFlowPreferenceFromUri" must be prefixed with an underscore
Loading history...
Coding Style introduced by
Missing doc comment for function getFlowPreferenceFromUri()
Loading history...
102
    {
103
        $this->logger->info(sprintf('Analysing uri "%s" for activation flow query parameter', $uri));
104
105
        $parts = parse_url($uri);
106
        /** @var array<string, string> $parameters */
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...
107
        $parameters = [];
108
109
        if (array_key_exists('query', $parts)) {
110
            $this->logger->debug('Found a query string in the uri');
111
            parse_str($parts['query'], $parameters);
112
        }
113
114
        // Is the configured field name in the querystring?
115
        if (!array_key_exists($this->fieldName, $parameters)) {
116
            $this->logger->notice(
117
                sprintf(
118
                    'The configured query string field name "%s" was not found in the uri "%s"',
119
                    $this->fieldName,
120
                    $uri
121
                )
122
            );
123
            return new ActivationFlowPreferenceNotExpressed();
124
        }
125
126
        $option = $parameters[$this->fieldName];
127
        switch ($option) {
128
            case 'ra':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
129
                return ActivationFlowPreference::createRa();
130
            case 'self':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
131
                return ActivationFlowPreference::createSelf();
132
        }
133
134
135
        $this->logger->notice(
136
            sprintf(
137
                'Field "%s" contained an invalid option "%s", must be one of: %s',
138
                $this->fieldName,
139
                $option,
140
                implode(', ', $this->options)
141
            )
142
        );
143
        return new ActivationFlowPreferenceNotExpressed();
144
    }
145
146
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
147
     * @return ActivationFlowPreferenceInterface[]
148
     */
149
    private function getFlowPreferencesFromSamlAttributes(): array
0 ignored issues
show
Coding Style introduced by
Private method name "ActivationFlowService::getFlowPreferencesFromSamlAttributes" must be prefixed with an underscore
Loading history...
150
    {
151
        $this->logger->info('Analysing saml entitlement attributes for allowed activation flows');
152
153
        $token = $this->tokenStorage->getToken();
154
        if (!$token instanceof TokenInterface) {
155
            throw new LogicException("A authentication token should be set at this point");
156
        }
157
158
        $activationFlows = [];
159
        $attributes = $token->getAttributes();
160
        if (array_key_exists(self::ACTIVATION_FLOW_ENTITLEMENT_SAML_ATTRIBUTE, $attributes)) {
161
            $this->logger->debug('Found entitlement saml attributes');
162
            if (in_array($this->attributes['ra'], $attributes[self::ACTIVATION_FLOW_ENTITLEMENT_SAML_ATTRIBUTE])) {
163
                $activationFlows[] = ActivationFlowPreference::createRa();
164
            }
165
            if (in_array($this->attributes['self'], $attributes[self::ACTIVATION_FLOW_ENTITLEMENT_SAML_ATTRIBUTE])) {
166
                $activationFlows[] = ActivationFlowPreference::createSelf();
167
            }
168
        }
169
        return $activationFlows;
170
    }
171
}
172