Security::acl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * This file is part of web-stack
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\WebStack\Domain\Security;
13
14
use Psr\Container\ContainerExceptionInterface;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\ServerRequestInterface;
17
use Slick\Di\Exception\NotFoundException;
18
use Slick\Http\Session\SessionDriverInterface;
19
use Slick\WebStack\Domain\Security\Authentication\Token\TokenStorageInterface;
20
use Slick\WebStack\Domain\Security\Authentication\Token\UserToken;
21
use Slick\WebStack\Domain\Security\Authentication\TokenInterface;
22
use Slick\WebStack\Domain\Security\Exception\UserNotFoundException;
23
use Slick\WebStack\Domain\Security\Http\SecurityProfile\SecurityProfileTrait;
24
use Slick\WebStack\Domain\Security\Http\SecurityProfile\StatefulSecurityProfile\SessionSecurityProfile;
25
use Slick\WebStack\Domain\Security\Http\SecurityProfile\StatefulSecurityProfileInterface;
26
use Slick\WebStack\Domain\Security\Http\SecurityProfileFactory;
27
28
/**
29
 * Security
30
 *
31
 * @template-covariant TUser of UserInterface
32
 * @package Slick\WebStack\Domain\Security
33
 *
34
 * @template-implements AuthorizationCheckerInterface<TUser>
35
 */
36
final class Security implements AuthorizationCheckerInterface, SecurityAuthenticatorInterface
37
{
38
    use SecurityProfileTrait;
39
40
    /**
41
     * @var array<string, mixed>
42
     */
43
    private static array $defaultOptions = [
44
        'enabled' => false,
45
        'accessControl' => []
46
    ];
47
48
    /**
49
     * @var array<string, mixed>
50
     */
51
    private array $options;
52
53
    /**
54
     * @var array<string>
55
     */
56
    private array $errors = [];
57
    private ?Http\SecurityProfileInterface $securityProfile = null;
58
59
    /**
60
     * Creates a Security
61
     *
62
     * @param SecurityProfileFactory $profileFactory
63
     * @param TokenStorageInterface<TUser> $tokenStorage
64
     * @param array<string, mixed> $options
65
     * @param SessionDriverInterface|null $sessionDriver
66
     */
67
    public function __construct(
68
        private readonly SecurityProfileFactory $profileFactory,
69
        private readonly TokenStorageInterface $tokenStorage,
70
        array $options = [],
71
        private readonly ?SessionDriverInterface $sessionDriver = null
72
    ) {
73
        $this->options = array_merge(self::$defaultOptions, $options);
74
    }
75
76
    /**
77
     * @inheritDoc
78
     */
79
    
80
    public function isGranted(string|array $attribute): bool
81
    {
82
        if (!$token = $this->tokenStorage->getToken()) {
83
            return false;
84
        }
85
86
        if (is_array($attribute)) {
0 ignored issues
show
introduced by
The condition is_array($attribute) is always true.
Loading history...
87
            return $this->checkAttributesList($attribute);
88
        }
89
90
        return $this->checkRole($token, $attribute) || $this->checkTokenAttributes($token, $attribute);
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    
97
    public function isGrantedAcl(ServerRequestInterface $request): bool
98
    {
99
        if (count($this->acl()) > 0 && !$this->tokenStorage->getToken()) {
100
            return false;
101
        }
102
103
        foreach ($this->acl() as $pattern => $attributes) {
104
            $this->matchExp = $pattern;
105
            if ($this->match($request)) {
106
                return $this->isGranted($attributes);
107
            }
108
        }
109
        return true;
110
    }
111
112
    /**
113
     * @inheritDoc
114
     */
115
    
116
    public function enabled(): bool
117
    {
118
        return $this->options['enabled'];
119
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124
    
125
    public function acl(): array
126
    {
127
        return $this->options['accessControl'];
128
    }
129
130
    /**
131
     * @inheritDoc
132
     * @throws NotFoundException|ContainerExceptionInterface
133
     */
134
    public function process(ServerRequestInterface $request): ?ResponseInterface
135
    {
136
        $securityProfile = $this->profileFactory->createProfile($this->options, $request);
137
        if ($securityProfile) {
138
            $this->securityProfile = $securityProfile;
139
            $this->options['accessControl'] = $securityProfile->acl();
140
            if ($securityProfile instanceof StatefulSecurityProfileInterface &&
141
                $securityProfile->restoreToken()
142
            ) {
143
                return null;
144
            }
145
146
            $process = $securityProfile->process($request);
147
            $this->errors = $securityProfile->authenticationErrors();
148
            return $process;
149
        }
150
151
        return null;
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     * @return TUser|UserInterface
0 ignored issues
show
Bug introduced by
The type Slick\WebStack\Domain\Security\TUser 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...
157
     */
158
    
159
    public function authenticatedUser(): UserInterface
160
    {
161
        $user = $this->user();
162
        if ($user instanceof UserInterface) {
163
            return $user;
164
        }
165
166
        throw new UserNotFoundException('User not authenticated.');
167
    }
168
169
    /**
170
     * Checks if given token has a specific attribute.
171
     *
172
     * @param TokenInterface $token The token to check.
173
     * @param string $attribute The attribute to look for.
174
     *
175
     * @template T of UserInterface
176
     * @phpstan-param TokenInterface<T> $token
177
     *
178
     * @return bool Returns true if the token has the attribute, false otherwise.
179
     */
180
    public function checkTokenAttributes(TokenInterface $token, string $attribute): bool
181
    {
182
        foreach ($token->attributes() as $key => $value) {
183
            if ($key === $attribute) {
184
                return (bool) $value;
185
            }
186
        }
187
        return false;
188
    }
189
190
    /**
191
     * Checks if a given role is present in the token's role list.
192
     *
193
     * @param TokenInterface $token The token to check
194
     * @param string $role The role to check
195
     * @return bool Returns true if the role is present, otherwise false
196
     *
197
     * @template T of UserInterface
198
     * @phpstan-param TokenInterface<T> $token
199
     */
200
    public function checkRole(TokenInterface $token, string $role): bool
201
    {
202
        $roles = $token->roleNames();
203
        return in_array($role, $roles);
204
    }
205
206
    /**
207
     * Check if any attribute in the given list is granted.
208
     *
209
     * @param array<string> $attribute The list of attributes to check.
210
     *
211
     * @return bool Returns true if any attribute is granted, otherwise false.
212
     */
213
    public function checkAttributesList(array $attribute): bool
214
    {
215
        foreach ($attribute as $attr) {
216
            if ($this->isGranted($attr)) {
217
                return true;
218
            }
219
        }
220
        return false;
221
    }
222
223
    /**
224
     * @inheritDoc
225
     */
226
    
227
    public function authenticationErrors(): array
228
    {
229
        return $this->errors;
230
    }
231
232
    public function user(): ?UserInterface
233
    {
234
        $token = $this->tokenStorage->getToken();
235
        if ($token instanceof TokenInterface) {
236
            $user = $token->user();
237
            if ($user instanceof UserInterface) {
238
                return $user;
239
            }
240
        }
241
        return null;
242
    }
243
244
    /**
245
     * Logs in the user using the provided UserInterface.
246
     *
247
     * @param UserInterface $user The user to log in.
248
     * @return void
249
     */
250
    public function login(UserInterface $user): void
251
    {
252
        if ($this->securityProfile instanceof StatefulSecurityProfileInterface) {
253
            $token = new UserToken($user);
254
            $token->withAttributes([
255
                'IS_AUTHENTICATED_FULLY' => 'true',
256
                'IS_AUTHENTICATED_REMEMBERED' => 'true',
257
                'IS_AUTHENTICATED' => 'true'
258
            ]);
259
            $this->securityProfile->login($token);
0 ignored issues
show
Bug introduced by
The method login() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

259
            $this->securityProfile->/** @scrutinizer ignore-call */ 
260
                                    login($token);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method login() does not exist on Slick\WebStack\Domain\Se...ecurityProfileInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Slick\WebStack\Domain\Se...Profile\SecurityProfile. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

259
            $this->securityProfile->/** @scrutinizer ignore-call */ 
260
                                    login($token);
Loading history...
260
        }
261
    }
262
263
    /**
264
     * @inheritDoc
265
     * @SuppressWarnings(PHPMD)
266
     */
267
    public function logout(): void
268
    {
269
        $this->sessionDriver?->erase(SessionSecurityProfile::SESSION_KEY);
270
271
        $profile = $this->profileFactory->profile();
272
        if ($profile instanceof StatefulSecurityProfileInterface) {
273
            $profile->logout();
274
        }
275
        $_SESSION = [];
276
    }
277
278
    /**
279
     * @inheritDoc
280
     */
281
    public function processEntryPoint(ServerRequestInterface $request): ?ResponseInterface
282
    {
283
        return $this->securityProfile?->processEntryPoint($request);
284
    }
285
}
286