• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FastyBird / simple-auth / 10019980238

20 Jul 2024 11:33AM UTC coverage: 68.883% (+2.3%) from 66.621%
10019980238

push

github

web-flow
Using custom database adapter for casbin (#8)

48 of 64 new or added lines in 4 files covered. (75.0%)

1 existing line in 1 file now uncovered.

518 of 752 relevant lines covered (68.88%)

4.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.87
/src/Security/AnnotationChecker.php
1
<?php declare(strict_types = 1);
2

3
/**
4
 * AnnotationChecker.php
5
 *
6
 * @license        More in LICENSE.md
7
 * @copyright      https://www.fastybird.com
8
 * @author         Adam Kadlec <adam.kadlec@fastybird.com>
9
 * @package        FastyBird:SimpleAuth!
10
 * @subpackage     Security
11
 * @since          0.1.0
12
 *
13
 * @date           29.08.20
14
 */
15

16
namespace FastyBird\SimpleAuth\Security;
17

18
use Casbin;
19
use FastyBird\SimpleAuth;
20
use FastyBird\SimpleAuth\Exceptions;
21
use ReflectionClass;
22
use ReflectionException;
23
use Reflector;
24
use function array_key_exists;
25
use function assert;
26
use function call_user_func;
27
use function class_exists;
28
use function count;
29
use function end;
30
use function in_array;
31
use function is_bool;
32
use function is_callable;
33
use function is_string;
34
use function preg_match_all;
35
use function preg_quote;
36
use function preg_split;
37
use function strtolower;
38
use function strval;
39
use const PREG_SPLIT_NO_EMPTY;
40

41
/**
42
 * Class security annotation checker
43
 *
44
 * @package        FastyBird:SimpleAuth!
45
 * @subpackage     Security
46
 *
47
 * @author         Adam Kadlec <adam.kadlec@fastybird.com>
48
 */
49
class AnnotationChecker
50
{
51

52
        public function __construct(private readonly Casbin\Enforcer $enforcer)
53
        {
54
        }
12✔
55

56
        /**
57
         * @param class-string $controllerClass
58
         *
59
         * @throws Exceptions\InvalidArgument
60
         */
61
        public function checkAccess(
62
                User|null $user,
63
                string $controllerClass,
64
                string|null $controllerMethod,
65
        ): bool
66
        {
67
                try {
68
                        if (class_exists($controllerClass)) {
12✔
69
                                $reflection = new ReflectionClass($controllerClass);
12✔
70

71
                                if ($controllerMethod !== null) {
12✔
72
                                        foreach ([$reflection, $reflection->getMethod($controllerMethod)] as $element) {
12✔
73
                                                if (!$this->isAllowed($user, $element)) {
12✔
74
                                                        return false;
6✔
75
                                                }
76
                                        }
77
                                } else {
78
                                        if (!$this->isAllowed($user, $reflection)) {
×
79
                                                return false;
6✔
80
                                        }
81
                                }
82
                        }
83
                } catch (ReflectionException) {
×
84
                        return false;
×
85
                }
86

87
                return true;
6✔
88
        }
89

90
        /**
91
         * @throws Exceptions\InvalidArgument
92
         */
93
        private function isAllowed(User|null $user, Reflector $element): bool
94
        {
95
                if ($this->parseAnnotation($element, 'Secured') === null) {
12✔
96
                        return true;
12✔
97
                }
98

99
                if ($user === null) {
12✔
100
                        return false;
×
101
                }
102

103
                // Check annotations only if element have to be secured
104
                return $this->checkUser($user, $element) && $this->checkRoles($user, $element);
12✔
105
        }
106

107
        /**
108
         * @return array<mixed>|null
109
         */
110
        private function parseAnnotation(Reflector $ref, string $name): array|null
111
        {
112
                $callable = [$ref, 'getDocComment'];
12✔
113

114
                if (!is_callable($callable)) {
12✔
115
                        return null;
×
116
                }
117

118
                $result = preg_match_all(
12✔
119
                        '#[\s*]@' . preg_quote($name, '#') . '(?:\(\s*([^)]*)\s*\)|\s|$)#',
12✔
120
                        strval(call_user_func($callable)),
12✔
121
                        $m,
12✔
122
                );
12✔
123

124
                if ($result === false || $result === 0) {
12✔
125
                        return null;
12✔
126
                }
127

128
                static $tokens = ['true' => true, 'false' => false, 'null' => null];
12✔
129

130
                $res = [];
12✔
131

132
                foreach ($m[1] as $s) {
12✔
133
                        $items = preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY);
12✔
134

135
                        foreach ($items !== false ? $items : ['true'] as $item) {
12✔
136
                                $tmp = strtolower($item);
12✔
137

138
                                if (!array_key_exists($tmp, $tokens) && $item !== '') {
12✔
139
                                        $res[] = $item;
12✔
140
                                }
141
                        }
142
                }
143

144
                return $res;
12✔
145
        }
146

147
        /**
148
         * @throws Exceptions\InvalidArgument
149
         */
150
        private function checkUser(User $user, Reflector $element): bool
151
        {
152
                // Check if element has @Secured\User annotation
153
                if ($this->parseAnnotation($element, 'Secured\User') !== null) {
12✔
154
                        // Get user annotation
155
                        $result = $this->parseAnnotation($element, 'Secured\User');
9✔
156

157
                        if (count($result) > 0) {
9✔
158
                                $userAnnotation = end($result);
9✔
159

160
                                // Annotation is single string
161
                                if (in_array($userAnnotation, ['loggedIn', 'guest'], true)) {
9✔
162
                                        // User have to be logged in and is not
163
                                        if ($userAnnotation === 'loggedIn' && $user->isLoggedIn() === false) {
9✔
164
                                                return false;
3✔
165

166
                                                // User have to be logged out and is logged in
167
                                        } elseif ($userAnnotation === 'guest' && $user->isLoggedIn() === true) {
6✔
UNCOV
168
                                                return false;
2✔
169
                                        }
170

171
                                        // Annotation have wrong definition
172
                                } else {
173
                                        throw new Exceptions\InvalidArgument(
×
174
                                                'In @Security\User annotation is allowed only one from two strings: \'loggedIn\' & \'guest\'',
×
175
                                        );
×
176
                                }
177
                        }
178

179
                        return true;
6✔
180
                }
181

182
                return true;
3✔
183
        }
184

185
        private function checkRoles(User $user, Reflector $element): bool
186
        {
187
                // Check if element has @Secured\Role annotation
188
                if ($this->parseAnnotation($element, 'Secured\Role') !== null) {
9✔
189
                        $rolesAnnotation = $this->parseAnnotation($element, 'Secured\Role');
3✔
190

191
                        foreach ($rolesAnnotation as $role) {
3✔
192
                                // Check if role name is defined
193
                                if (is_bool($role) || $role === null) {
3✔
194
                                        continue;
×
195
                                }
196

197
                                assert(is_string($role));
198

199
                                if (
200
                                        $this->enforcer->hasRoleForUser(
3✔
201
                                                $user->getId()?->toString() ?? SimpleAuth\Constants::USER_ANONYMOUS,
3✔
202
                                                $role,
3✔
203
                                        )
3✔
204
                                ) {
205
                                        return true;
×
206
                                }
207
                        }
208

209
                        return false;
3✔
210
                }
211

212
                return true;
6✔
213
        }
214

215
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc