Vectorface /
auth
| 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
Loading history...
|
|||||
| 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