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