1 | <?php |
||||
2 | |||||
3 | namespace Vectorface\Auth; |
||||
4 | |||||
5 | use ArrayAccess; |
||||
6 | use Psr\Log\LoggerAwareTrait; |
||||
7 | use Psr\Log\LoggerInterface; |
||||
8 | use Vectorface\Auth\Plugin\AuthPluginInterface; |
||||
9 | use Exception; |
||||
10 | |||||
11 | /** |
||||
12 | * A class representing a loose Authentication and Authorization framework. |
||||
13 | * - Authentication is handled explicitly with login, logout, and verify functions. |
||||
14 | * - Authorization is handled in whatever capacity is implemented by loaded plugins. |
||||
15 | * |
||||
16 | * Plugin classes can share data using the Auth class itself as a shared source of data. |
||||
17 | */ |
||||
18 | class Auth implements ArrayAccess |
||||
19 | { |
||||
20 | /** |
||||
21 | * Provides setLogger method, and protected logger property. |
||||
22 | */ |
||||
23 | use LoggerAwareTrait; |
||||
24 | |||||
25 | /** |
||||
26 | * Name of the method called when authenticating. |
||||
27 | */ |
||||
28 | const ACTION_LOGIN = 'login'; |
||||
29 | |||||
30 | /** |
||||
31 | * Name of the method called when attempting to log out. |
||||
32 | */ |
||||
33 | const ACTION_LOGOUT = 'logout'; |
||||
34 | |||||
35 | /** |
||||
36 | * Name of the session verification method. |
||||
37 | */ |
||||
38 | const ACTION_VERIFY = 'verify'; |
||||
39 | |||||
40 | /** |
||||
41 | * A result that has no effect either positive or negative. |
||||
42 | */ |
||||
43 | const RESULT_NOOP = null; |
||||
44 | |||||
45 | /** |
||||
46 | * A result that acts as a provisional success; Pass if all other tests pass. |
||||
47 | */ |
||||
48 | const RESULT_SUCCESS = 0; |
||||
49 | |||||
50 | /** |
||||
51 | * A result that indicates forced immediate success, bypassing further tests. Should not be used lightly. |
||||
52 | */ |
||||
53 | const RESULT_FORCE = 1; |
||||
54 | |||||
55 | /** |
||||
56 | * A result that indicates failure. All failures are immediate. |
||||
57 | */ |
||||
58 | const RESULT_FAILURE = -1; |
||||
59 | |||||
60 | /** |
||||
61 | * An array of auth plugins. |
||||
62 | * |
||||
63 | * @var AuthPluginInterface[] |
||||
64 | */ |
||||
65 | private $plugins = []; |
||||
66 | |||||
67 | /** |
||||
68 | * Shared values, accessible using the ArrayAccess interface. |
||||
69 | * |
||||
70 | * @var mixed[] |
||||
71 | */ |
||||
72 | private $shared = []; |
||||
73 | |||||
74 | /** |
||||
75 | * Add a plugin to the Auth module. |
||||
76 | * |
||||
77 | * @param string|AuthPluginInterface The name of a plugin class to be registered, or a configured instance of a |
||||
0 ignored issues
–
show
|
|||||
78 | * security plugin. |
||||
79 | * @return bool True if the plugin was added successfully. |
||||
80 | 11 | */ |
|||
81 | public function addPlugin($plugin) |
||||
82 | 11 | { |
|||
83 | 1 | if (!($plugin instanceof AuthPluginInterface)) { |
|||
84 | 1 | if (!is_string($plugin)) { |
|||
85 | return false; |
||||
86 | 1 | } |
|||
87 | if (!in_array(AuthPluginInterface::class, class_implements($plugin, false))) { |
||||
88 | return false; |
||||
89 | 1 | } |
|||
90 | $plugin = new $plugin(); |
||||
91 | 11 | } |
|||
92 | 11 | $plugin->setAuth($this); |
|||
93 | 11 | $this->plugins[] = $plugin; |
|||
94 | return true; |
||||
95 | } |
||||
96 | |||||
97 | /** |
||||
98 | * Perform a login based on username and password. |
||||
99 | * |
||||
100 | * @param string $username A user identifier. |
||||
101 | * @param string $password The user's password. |
||||
102 | * @return bool True if the login was successful, false otherwise. |
||||
103 | * @throws AuthException |
||||
104 | 8 | */ |
|||
105 | public function login($username, $password) |
||||
106 | 8 | { |
|||
107 | return $this->action(self::ACTION_LOGIN, [$username, $password]); |
||||
108 | } |
||||
109 | |||||
110 | /** |
||||
111 | * Attempt to log the user out. |
||||
112 | * |
||||
113 | * @throws AuthException |
||||
114 | 2 | */ |
|||
115 | public function logout() |
||||
116 | 2 | { |
|||
117 | return $this->action(self::ACTION_LOGOUT); |
||||
118 | } |
||||
119 | |||||
120 | /** |
||||
121 | * Verify a user's saved data - check if the user is logged in. |
||||
122 | * |
||||
123 | * @return bool True if the user's session was verified successfully. |
||||
124 | * @throws AuthException |
||||
125 | 4 | */ |
|||
126 | public function verify() |
||||
127 | 4 | { |
|||
128 | return $this->action(self::ACTION_VERIFY); |
||||
129 | } |
||||
130 | |||||
131 | /** |
||||
132 | * Call an action on a plugin, with particular arguments. |
||||
133 | * |
||||
134 | * @param AuthPluginInterface $plugin The plugin on which to run the action. |
||||
135 | * @param string $action The plugin action, login, logout, or verify. |
||||
136 | * @param mixed[] $arguments A list of arguments to pass to the action. |
||||
137 | * @return int The result returned by the Auth plugin. |
||||
138 | * @throws AuthException |
||||
139 | 9 | */ |
|||
140 | private function callPlugin(AuthPluginInterface $plugin, $action, $arguments) |
||||
141 | { |
||||
142 | /* This is a bit of defensive programming; It is not possible to hit this code. */ |
||||
143 | // @codeCoverageIgnoreStart |
||||
144 | if (!in_array($action, array(self::ACTION_LOGIN, self::ACTION_LOGOUT, self::ACTION_VERIFY))) { |
||||
145 | throw new AuthException("Attempted to perform unknown action: $action"); |
||||
146 | } |
||||
147 | // @codeCoverageIgnoreEnd |
||||
148 | 9 | ||||
149 | $result = call_user_func_array([$plugin, $action], $arguments); |
||||
150 | |||||
151 | 9 | /* Expected results are an integer or null; Anything else is incorrect. */ |
|||
152 | 1 | if (!(is_int($result) || $result === null)) { |
|||
153 | 1 | throw new AuthException(sprintf( |
|||
154 | "Unknown %s result in %s plugin: %s", |
||||
155 | 1 | $action, |
|||
156 | 1 | get_class($plugin), |
|||
157 | print_r($result, true) |
||||
0 ignored issues
–
show
It seems like
print_r($result, true) can also be of type true ; however, parameter $values of sprintf() does only seem to accept double|integer|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
158 | )); |
||||
159 | } |
||||
160 | 9 | ||||
161 | return $result; |
||||
162 | } |
||||
163 | |||||
164 | /** |
||||
165 | * Perform a generalized action: login, logout, or verify: Run said function on each plugin in order verifying |
||||
166 | * result along the way. |
||||
167 | * |
||||
168 | * @param string $action The action to be taken. One of login, logout, or verify. |
||||
169 | * @param mixed[] $arguments A list of arguments to pass to the action. |
||||
170 | * @return bool True if the action was successful, false otherwise. |
||||
171 | * @throws AuthException |
||||
172 | 9 | */ |
|||
173 | private function action($action, $arguments = []) |
||||
174 | 9 | { |
|||
175 | $success = false; |
||||
176 | 9 | ||||
177 | foreach ($this->plugins as $plugin) { |
||||
178 | 9 | try { |
|||
179 | 1 | $result = $this->callPlugin($plugin, $action, $arguments); |
|||
180 | 1 | } catch (AuthException $e) { |
|||
181 | 1 | throw $e; |
|||
182 | 1 | } catch (Exception $e) { |
|||
183 | 1 | $this->logException($e, "Fatal %s error in %s plugin", $action, get_class($plugin)); |
|||
184 | return false; |
||||
185 | } |
||||
186 | 9 | ||||
187 | 7 | if ($result === self::RESULT_SUCCESS /* 0 */) { |
|||
188 | 7 | $success = true; |
|||
189 | } elseif ($result !== self::RESULT_NOOP) { |
||||
190 | 5 | /* If not noop or success, possible options are hard fail (<=-1), or hard success (>=1) */ |
|||
191 | return ($result >= self::RESULT_FORCE); |
||||
192 | } /* else result is NOOP. Do nothing. Result not defined for plugin. */ |
||||
193 | 7 | } |
|||
194 | return $success; |
||||
195 | } |
||||
196 | |||||
197 | /** |
||||
198 | * Passthrough function call for plugins. |
||||
199 | * |
||||
200 | * @param string $method The name of the method to be called. |
||||
201 | * @param array $args An array of arguments to be passed to the method. |
||||
202 | * @return mixed Returns whatever the passthrough function returns, or null or error or missing function. |
||||
203 | * @throws AuthException |
||||
204 | * @noinspection PhpRedundantCatchClauseInspection |
||||
205 | 5 | */ |
|||
206 | public function __call($method, $args = []) |
||||
207 | 5 | { |
|||
208 | 5 | foreach ($this->plugins as $plugin) { |
|||
209 | if (is_callable([$plugin, $method])) { |
||||
210 | 5 | try { |
|||
211 | 1 | return call_user_func_array([$plugin, $method], $args); |
|||
212 | 1 | } catch (AuthException $e) { |
|||
213 | 1 | throw $e; |
|||
214 | 1 | } catch (Exception $e) { |
|||
215 | $this->logException($e, "Exception caught calling %s->%s", get_class($plugin), $method); |
||||
216 | return null; |
||||
217 | } |
||||
218 | } |
||||
219 | 1 | } |
|||
220 | 1 | ||||
221 | if ($this->logger) { |
||||
222 | 1 | $this->logger->warning(__CLASS__ . ": $method not implemented by any loaded plugin"); |
|||
223 | } |
||||
224 | return null; |
||||
225 | } |
||||
226 | |||||
227 | /** |
||||
228 | * ArrayAccess offsetExists |
||||
229 | * |
||||
230 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php |
||||
231 | 1 | * @inheritDoc |
|||
232 | */ |
||||
233 | 1 | #[\ReturnTypeWillChange] |
|||
234 | public function offsetExists($offset) |
||||
235 | { |
||||
236 | return isset($this->shared[$offset]); |
||||
237 | } |
||||
238 | |||||
239 | /** |
||||
240 | * ArrayAccess offsetGet |
||||
241 | * |
||||
242 | 1 | * @link http://php.net/manual/en/arrayaccess.offsetget.php |
|||
243 | * @inheritDoc |
||||
244 | 1 | */ |
|||
245 | #[\ReturnTypeWillChange] |
||||
246 | public function offsetGet($offset) |
||||
247 | { |
||||
248 | return $this->shared[$offset] ?? null; |
||||
249 | } |
||||
250 | |||||
251 | /** |
||||
252 | * ArrayAccess offsetSet |
||||
253 | 1 | * |
|||
254 | * @link http://php.net/manual/en/arrayaccess.offsetset.php |
||||
255 | 1 | * @inheritDoc |
|||
256 | 1 | */ |
|||
257 | #[\ReturnTypeWillChange] |
||||
258 | public function offsetSet($offset, $value) |
||||
259 | { |
||||
260 | $this->shared[$offset] = $value; |
||||
261 | } |
||||
262 | |||||
263 | /** |
||||
264 | 1 | * ArrayAccess offsetUnset |
|||
265 | * |
||||
266 | 1 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php |
|||
267 | 1 | * @inheritDoc |
|||
268 | */ |
||||
269 | #[\ReturnTypeWillChange] |
||||
270 | public function offsetUnset($offset) |
||||
271 | { |
||||
272 | unset($this->shared[$offset]); |
||||
273 | } |
||||
274 | 2 | ||||
275 | /** |
||||
276 | 2 | * Get the logger instance set on this object |
|||
277 | * |
||||
278 | * @return LoggerInterface The logger for use in this Auth object, or null if not set. |
||||
279 | */ |
||||
280 | public function getLogger() |
||||
281 | { |
||||
282 | return $this->logger; /* Defined as part of the LoggerAwareInterface */ |
||||
283 | } |
||||
284 | |||||
285 | /** |
||||
286 | * Log a message and exception in a semi-consistent form. |
||||
287 | * |
||||
288 | * Logs the message, and appends exception message and location. |
||||
289 | 2 | * |
|||
290 | * @param Exception $exc The exception to log. |
||||
291 | 2 | * @param string $message The exception message in printf style. |
|||
292 | 2 | * @param string ... Any number of string parameters corresponding to %s placeholders in the message string. |
|||
293 | 2 | */ |
|||
294 | 2 | private function logException(Exception $exc, $message /*, ... */) |
|||
295 | 2 | { |
|||
296 | if ($this->logger) { |
||||
297 | 2 | $message .= ": %s (%s@%s)"; /* Append exception info to log string. */ |
|||
298 | $args = array_merge( |
||||
299 | [$message], |
||||
300 | array_slice(func_get_args(), 2), |
||||
301 | [$exc->getMessage(), $exc->getFile(), $exc->getLine()] |
||||
302 | ); |
||||
303 | $this->logger->warning(call_user_func_array('sprintf', $args)); |
||||
304 | } |
||||
305 | } |
||||
306 | } |
||||
307 |
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths