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

FastyBird / simple-auth / 10030791279

21 Jul 2024 07:23PM UTC coverage: 69.153% (+0.2%) from 68.983%
10030791279

push

github

web-flow
Move enforcer instance into factory (#12)

46 of 50 new or added lines in 4 files covered. (92.0%)

1 existing line in 1 file now uncovered.

547 of 791 relevant lines covered (69.15%)

5.68 hits per line

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

82.76
/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 FastyBird\SimpleAuth\Exceptions;
19
use ReflectionClass;
20
use ReflectionException;
21
use Reflector;
22
use function array_key_exists;
23
use function call_user_func;
24
use function class_exists;
25
use function count;
26
use function end;
27
use function in_array;
28
use function is_bool;
29
use function is_callable;
30
use function is_string;
31
use function preg_match_all;
32
use function preg_quote;
33
use function preg_split;
34
use function strtolower;
35
use function strval;
36
use const PREG_SPLIT_NO_EMPTY;
37

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

49
        /**
50
         * @param class-string $controllerClass
51
         *
52
         * @throws Exceptions\InvalidArgument
53
         * @throws Exceptions\InvalidState
54
         */
55
        public function checkAccess(
56
                User|null $user,
57
                string $controllerClass,
58
                string|null $controllerMethod,
59
        ): bool
60
        {
61
                try {
62
                        if (class_exists($controllerClass)) {
12✔
63
                                $reflection = new ReflectionClass($controllerClass);
12✔
64

65
                                if ($controllerMethod !== null) {
12✔
66
                                        foreach ([$reflection, $reflection->getMethod($controllerMethod)] as $element) {
12✔
67
                                                if (!$this->isAllowed($user, $element)) {
12✔
68
                                                        return false;
6✔
69
                                                }
70
                                        }
71
                                } else {
72
                                        if (!$this->isAllowed($user, $reflection)) {
×
73
                                                return false;
6✔
74
                                        }
75
                                }
76
                        }
77
                } catch (ReflectionException) {
×
78
                        return false;
×
79
                }
80

81
                return true;
6✔
82
        }
83

84
        /**
85
         * @throws Exceptions\InvalidArgument
86
         * @throws Exceptions\InvalidState
87
         */
88
        private function isAllowed(User|null $user, Reflector $element): bool
89
        {
90
                if ($this->parseAnnotation($element, 'Secured') === null) {
12✔
91
                        return true;
12✔
92
                }
93

94
                if ($user === null) {
12✔
95
                        return false;
×
96
                }
97

98
                // Check annotations only if element have to be secured
99
                return $this->checkUser($user, $element) && $this->checkRoles($user, $element);
12✔
100
        }
101

102
        /**
103
         * @return array<mixed>|null
104
         */
105
        private function parseAnnotation(Reflector $ref, string $name): array|null
106
        {
107
                $callable = [$ref, 'getDocComment'];
12✔
108

109
                if (!is_callable($callable)) {
12✔
110
                        return null;
×
111
                }
112

113
                $result = preg_match_all(
12✔
114
                        '#[\s*]@' . preg_quote($name, '#') . '(?:\(\s*([^)]*)\s*\)|\s|$)#',
12✔
115
                        strval(call_user_func($callable)),
12✔
116
                        $m,
12✔
117
                );
12✔
118

119
                if ($result === false || $result === 0) {
12✔
120
                        return null;
12✔
121
                }
122

123
                static $tokens = ['true' => true, 'false' => false, 'null' => null];
12✔
124

125
                $res = [];
12✔
126

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

130
                        foreach ($items !== false ? $items : ['true'] as $item) {
12✔
131
                                $tmp = strtolower($item);
12✔
132

133
                                if (!array_key_exists($tmp, $tokens) && $item !== '') {
12✔
134
                                        $res[] = $item;
12✔
135
                                }
136
                        }
137
                }
138

139
                return $res;
12✔
140
        }
141

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

152
                        if (count($result) > 0) {
9✔
153
                                $userAnnotation = end($result);
9✔
154

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

161
                                                // User have to be logged out and is logged in
162
                                        } elseif ($userAnnotation === 'guest' && $user->isLoggedIn() === true) {
6✔
UNCOV
163
                                                return false;
2✔
164
                                        }
165

166
                                        // Annotation have wrong definition
167
                                } else {
168
                                        throw new Exceptions\InvalidArgument(
×
169
                                                'In @Security\User annotation is allowed only one from two strings: \'loggedIn\' & \'guest\'',
×
170
                                        );
×
171
                                }
172
                        }
173

174
                        return true;
6✔
175
                }
176

177
                return true;
3✔
178
        }
179

180
        /**
181
         * @throws Exceptions\InvalidState
182
         */
183
        private function checkRoles(User $user, Reflector $element): bool
184
        {
185
                // Check if element has @Secured\Role annotation
186
                if ($this->parseAnnotation($element, 'Secured\Role') !== null) {
9✔
187
                        $rolesAnnotation = $this->parseAnnotation($element, 'Secured\Role');
3✔
188

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

195
                                if (is_string($role) && $user->isInRole($role)) {
3✔
196
                                        return true;
×
197
                                }
198
                        }
199

200
                        return false;
3✔
201
                }
202

203
                return true;
6✔
204
        }
205

206
}
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