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

FastyBird / simple-auth / 7822099561

07 Feb 2024 10:39PM UTC coverage: 74.633% (-3.5%) from 78.119%
7822099561

push

github

akadlec
Use native enum

3 of 7 new or added lines in 3 files covered. (42.86%)

29 existing lines in 5 files now uncovered.

356 of 477 relevant lines covered (74.63%)

4.91 hits per line

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

85.19
/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
         */
54
        public function checkAccess(
55
                User $user,
56
                string $controllerClass,
57
                string $controllerMethod,
58
        ): bool
59
        {
60
                try {
61
                        if (class_exists($controllerClass)) {
12✔
62
                                $reflection = new ReflectionClass($controllerClass);
12✔
63

64
                                foreach ([$reflection, $reflection->getMethod($controllerMethod)] as $element) {
12✔
65
                                        if (!$this->isAllowed($user, $element)) {
12✔
66
                                                return false;
6✔
67
                                        }
68
                                }
69
                        }
70
                } catch (ReflectionException) {
×
71
                        return false;
×
72
                }
73

74
                return true;
6✔
75
        }
76

77
        /**
78
         * @throws Exceptions\InvalidArgument
79
         */
80
        private function isAllowed(User $user, Reflector $element): bool
81
        {
82
                // Check annotations only if element have to be secured
83
                return $this->parseAnnotation($element, 'Secured') !== null
12✔
84
                        ? $this->checkUser($user, $element)
12✔
85
                                && $this->checkRoles($user, $element)
12✔
86
                        : true;
12✔
87
        }
88

89
        /**
90
         * @return array<mixed>|null
91
         */
92
        private function parseAnnotation(Reflector $ref, string $name): array|null
93
        {
94
                $callable = [$ref, 'getDocComment'];
12✔
95

96
                if (!is_callable($callable)) {
12✔
97
                        return null;
×
98
                }
99

100
                $result = preg_match_all(
12✔
101
                        '#[\s*]@' . preg_quote($name, '#') . '(?:\(\s*([^)]*)\s*\)|\s|$)#',
12✔
102
                        strval(call_user_func($callable)),
12✔
103
                        $m,
12✔
104
                );
12✔
105

106
                if ($result === false || $result === 0) {
12✔
107
                        return null;
12✔
108
                }
109

110
                static $tokens = ['true' => true, 'false' => false, 'null' => null];
12✔
111

112
                $res = [];
12✔
113

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

117
                        foreach ($items !== false ? $items : ['true'] as $item) {
12✔
118
                                $tmp = strtolower($item);
12✔
119

120
                                if (!array_key_exists($tmp, $tokens) && $item !== '') {
12✔
121
                                        $res[] = $item;
12✔
122
                                }
123
                        }
124
                }
125

126
                return $res;
12✔
127
        }
128

129
        /**
130
         * @throws Exceptions\InvalidArgument
131
         */
132
        private function checkUser(User $user, Reflector $element): bool
133
        {
134
                // Check if element has @Secured\User annotation
135
                if ($this->parseAnnotation($element, 'Secured\User') !== null) {
12✔
136
                        // Get user annotation
137
                        $result = $this->parseAnnotation($element, 'Secured\User');
9✔
138

139
                        if (count($result) > 0) {
9✔
140
                                $userAnnotation = end($result);
9✔
141

142
                                // Annotation is single string
143
                                if (in_array($userAnnotation, ['loggedIn', 'guest'], true)) {
9✔
144
                                        // User have to be logged in and is not
145
                                        if ($userAnnotation === 'loggedIn' && $user->isLoggedIn() === false) {
9✔
146
                                                return false;
3✔
147

148
                                                // User have to be logged out and is logged in
149
                                        } elseif ($userAnnotation === 'guest' && $user->isLoggedIn() === true) {
6✔
150
                                                return false;
2✔
151
                                        }
152

153
                                        // Annotation have wrong definition
154
                                } else {
155
                                        throw new Exceptions\InvalidArgument(
×
156
                                                'In @Security\User annotation is allowed only one from two strings: \'loggedIn\' & \'guest\'',
×
UNCOV
157
                                        );
×
158
                                }
159
                        }
160

161
                        return true;
6✔
162
                }
163

164
                return true;
3✔
165
        }
166

167
        private function checkRoles(User $user, Reflector $element): bool
168
        {
169
                // Check if element has @Secured\Role annotation
170
                if ($this->parseAnnotation($element, 'Secured\Role') !== null) {
9✔
171
                        $rolesAnnotation = $this->parseAnnotation($element, 'Secured\Role');
3✔
172

173
                        foreach ($rolesAnnotation as $role) {
3✔
174
                                // Check if role name is defined
175
                                if (is_bool($role) || $role === null) {
3✔
176
                                        continue;
×
177
                                }
178

179
                                if (is_string($role) && $user->isInRole($role)) {
3✔
180
                                        return true;
×
181
                                }
182
                        }
183

184
                        return false;
3✔
185
                }
186

187
                return true;
6✔
188
        }
189

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

© 2026 Coveralls, Inc