assertRegistrationOfSelfAssertedTokensIsAllowed()   B
last analyzed

Complexity

Conditions 9
Paths 10

Size

Total Lines 46
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 24
nc 10
nop 1
dl 0
loc 46
rs 8.0555
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright 2022 SURFnet B.V.
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
 */
0 ignored issues
show
Coding Style introduced by
Missing @link tag in file comment
Loading history...
18
19
namespace Surfnet\StepupMiddleware\ApiBundle\Authorization\Service;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\Stepup\Configuration\Value\Institution;
23
use Surfnet\Stepup\Identity\Value\IdentityId;
24
use Surfnet\Stepup\Identity\Value\VettingType;
25
use Surfnet\StepupMiddleware\ApiBundle\Authorization\Value\AuthorizationDecision;
0 ignored issues
show
Bug introduced by
The type Surfnet\StepupMiddleware...e\AuthorizationDecision 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...
26
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Entity\InstitutionConfigurationOptions;
27
use Surfnet\StepupMiddleware\ApiBundle\Configuration\Service\InstitutionConfigurationOptionsService;
28
use Surfnet\StepupMiddleware\ApiBundle\Identity\Entity\Identity;
29
use Surfnet\StepupMiddleware\ApiBundle\Identity\Query\VettedSecondFactorQuery;
30
use Surfnet\StepupMiddleware\ApiBundle\Identity\Service\IdentityService;
31
use Surfnet\StepupMiddleware\ApiBundle\Identity\Service\RecoveryTokenService;
32
use Surfnet\StepupMiddleware\ApiBundle\Identity\Service\SecondFactorService;
33
34
/**
35
 * Perform authorization checks
36
 * For example, test if an identity is allowed to register self-asserted tokens.
37
 *
38
 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
39
 */
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...
40
class AuthorizationService
41
{
42
    public function __construct(
43
        private readonly IdentityService $identityService,
44
        private readonly InstitutionConfigurationOptionsService $institutionConfigurationService,
45
        private readonly SecondFactorService $secondFactorService,
46
        private readonly RecoveryTokenService $recoveryTokenService
47
    ) {
48
    }
49
50
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $identityId should have a doc-comment as per coding-style.
Loading history...
51
     * Is an identity is allowed to register a self asserted token.
52
     *
53
     * An identity can register a SAT when:
54
     * - The institution of the identity allows SAT
55
     * - Has not yet registered a SAT
56
     * - Has not possessed a non SAT token previously.
57
     * - It did not lose both the recovery token and self-asserted token
58
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
59
    public function assertRegistrationOfSelfAssertedTokensIsAllowed(IdentityId $identityId): AuthorizationDecision
60
    {
61
        $identity = $this->findIdentity($identityId);
62
        if (!$identity instanceof Identity) {
63
            return $this->deny('Identity not found');
64
        }
65
        $institutionConfiguration = $this->findInstitutionConfiguration($identity);
66
67
        if (!$institutionConfiguration instanceof InstitutionConfigurationOptions) {
68
            return $this->deny(
69
                'Institution configuration could not be found, unable to ascertain if self-asserted tokens feature is enabled',
70
            );
71
        }
72
73
        if (!$institutionConfiguration->selfAssertedTokensOption->isEnabled()) {
74
            return $this->deny(
75
                sprintf('Institution "%s", does not allow self-asserted tokens', (string)$identity->institution),
76
            );
77
        }
78
79
        $hasVettedSecondFactorToken = $this->secondFactorService->hasVettedByIdentity($identityId);
80
81
        $options = $this->identityService->getSelfAssertedTokenRegistrationOptions(
82
            $identity,
83
            $hasVettedSecondFactorToken,
84
        );
85
86
        if ($hasVettedSecondFactorToken) {
87
            return $this->deny('Identity already has a vetted second factor');
88
        }
89
90
        // Only allow self-asserted token (SAT) if the user does not have a token yet, or the first
91
        // registered token was a SAT.
92
        $hadOtherTokenType = $options->possessedSelfAssertedToken === false && $options->possessedToken;
93
        if ($hadOtherTokenType) {
94
            return $this->deny(
95
                'Identity never possessed a self-asserted token, but did/does possess one of the other types',
96
            );
97
        }
98
        // The Identity is not allowed to do a SAT when he had a RT, but lost it. And also currently has no SF
99
        $hasActiveRecoveryToken = $this->recoveryTokenService->identityHasActiveRecoveryToken($identity);
100
        if ($options->possessedSelfAssertedToken && !$hasActiveRecoveryToken) {
101
            return $this->deny('Identity lost both Recovery and Second Factor token, SAT is not allowed');
102
        }
103
104
        return $this->allow();
105
    }
106
107
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $identityId should have a doc-comment as per coding-style.
Loading history...
108
     * Is an identity allowed to self vet using a self-asserted token?
109
     *
110
     * One is allowed to do so when:
111
     *  - SAT is allowed for the institution of the identity
112
     *  - All the tokens of the identity are vetted using the SAT vetting type
113
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
114
    public function assertSelfVetUsingSelfAssertedTokenIsAllowed(IdentityId $identityId): AuthorizationDecision
115
    {
116
        $identity = $this->findIdentity($identityId);
117
        if (!$identity instanceof Identity) {
118
            return $this->deny('Identity not found');
119
        }
120
        $institutionConfiguration = $this->findInstitutionConfiguration($identity);
121
122
        if (!$institutionConfiguration instanceof InstitutionConfigurationOptions) {
123
            return $this->deny(
124
                'Institution configuration could not be found, unable to ascertain if self-asserted tokens feature is enabled',
125
            );
126
        }
127
128
        if (!$institutionConfiguration->selfAssertedTokensOption->isEnabled()) {
129
            return $this->deny(
130
                sprintf('Institution "%s", does not allow self-asserted tokens', (string)$identity->institution),
131
            );
132
        }
133
134
        $query = new VettedSecondFactorQuery();
135
        $query->identityId = $identityId;
136
        $query->pageNumber = 1;
137
        $tokens = $this->secondFactorService->searchVettedSecondFactors($query);
138
        foreach ($tokens->getIterator() as $vettedToken) {
139
            if ($vettedToken->vettingType !== VettingType::TYPE_SELF_ASSERTED_REGISTRATION) {
140
                return $this->deny('Self-vetting using SAT is only allowed when only SAT tokens are in possession');
141
            }
142
        }
143
144
        return $this->allow();
145
    }
146
147
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $identityId should have a doc-comment as per coding-style.
Loading history...
148
     * Is an identity allowed to register recovery tokens?
149
     *
150
     * One is allowed to do so when:
151
     *  - SAT is allowed for the institution of the identity
152
     *  - Identity must possess a SAT (or did so at one point)
153
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
154
    public function assertRecoveryTokensAreAllowed(IdentityId $identityId): AuthorizationDecision
155
    {
156
        $identity = $this->findIdentity($identityId);
157
        if (!$identity instanceof Identity) {
158
            return $this->deny('Identity not found');
159
        }
160
        $institutionConfiguration = $this->findInstitutionConfiguration($identity);
161
162
        if (!$institutionConfiguration instanceof InstitutionConfigurationOptions) {
163
            return $this->deny(
164
                'Institution configuration could not be found, unable to ascertain if self-asserted tokens feature is enabled',
165
            );
166
        }
167
168
        if (!$institutionConfiguration->selfAssertedTokensOption->isEnabled()) {
169
            return $this->deny(
170
                sprintf('Institution "%s", does not allow self-asserted tokens', (string)$identity->institution),
171
            );
172
        }
173
174
        // Only allow CRUD actions on recovery tokens when the identity previously registered a SAT
175
        $options = $this->identityService->getSelfAssertedTokenRegistrationOptions(
176
            $identity,
177
            $this->secondFactorService->hasVettedByIdentity($identityId),
178
        );
179
        if ($options->possessedSelfAssertedToken === false) {
180
            return $this->deny(
181
                'Identity never possessed a self-asserted token, deny access to recovery token CRUD actions',
182
            );
183
        }
184
185
        return $this->allow();
186
    }
187
188
    private function findInstitutionConfiguration(Identity $identity): ?InstitutionConfigurationOptions
0 ignored issues
show
Coding Style introduced by
Private method name "AuthorizationService::findInstitutionConfiguration" must be prefixed with an underscore
Loading history...
189
    {
190
        $institution = new Institution((string)$identity->institution);
191
        return $this->institutionConfigurationService
192
            ->findInstitutionConfigurationOptionsFor($institution);
193
    }
194
195
    private function findIdentity(IdentityId $identityId): ?Identity
0 ignored issues
show
Coding Style introduced by
Private method name "AuthorizationService::findIdentity" must be prefixed with an underscore
Loading history...
196
    {
197
        return $this->identityService->find((string)$identityId);
198
    }
199
200
    private function deny(string $errorMessage): AuthorizationDecision
0 ignored issues
show
Coding Style introduced by
Private method name "AuthorizationService::deny" must be prefixed with an underscore
Loading history...
201
    {
202
        return AuthorizationDecision::denied([$errorMessage]);
203
    }
204
205
    private function allow(): AuthorizationDecision
0 ignored issues
show
Coding Style introduced by
Private method name "AuthorizationService::allow" must be prefixed with an underscore
Loading history...
206
    {
207
        return AuthorizationDecision::allowed();
208
    }
209
}
210