Passed
Branch master (372b2a)
by Filipe
01:32
created

Security::acl()   A

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