eesyphp/src/Auth.php

307 lines
9.2 KiB
PHP
Raw Normal View History

2023-02-25 05:02:27 +01:00
<?php
namespace EesyPHP;
class Auth {
/**
* Initialized methods
* @var array<string,string>
*/
private static $methods = array();
/**
* Method name used to authenticate current user
* @var string|null
*/
private static $logged_method = null;
/**
* Initialized backends
* @var array<string,string>
*/
private static $backends = array();
/**
* Current authenticated user
* @var \EesyPHP\Auth\User|null
*/
private static $user = null;
/**
* Initialize
* @return void
*/
public static function init() {
if (!self :: enabled()) return;
self :: $methods = array();
foreach(App::get('auth.methods', array(), 'array') as $method) {
if (!$method || !is_string($method)) {
Log::warning(
'Auth Init: Invalid auth method retreive from configuration, ignore it: %s',
vardump($method));
continue;
}
$class = (
$method[0] == '\\'?
$method:
"\\EesyPHP\\Auth\\".ucfirst($method)
);
if (!class_exists($class)) {
Log::warning(
"Auth Init: Unknown auth method '%s' retreived from configuration, ignore it",
$method
);
continue;
}
$parents = class_parents($class);
if (!is_array($parents) || !in_array('EesyPHP\\Auth\\Method', $parents)) {
Log::warning(
'Auth Init: Auth method %s class (%s) do not derivate from \\EesyPHP\\Auth\\Method '.
'class, ignore it.',
$method, $class);
continue;
}
if (!call_user_func(array($class, 'init'))) {
Log::warning(
'Auth Init: fail to initialize auth method %s, ignore it',
$method, $class);
continue;
}
Log::trace('Auth method %s initialized (class %s)', $method, $class);
self :: $methods[strtolower($method)] = $class;
}
self :: $backends = array();
foreach(App::get('auth.backends', array(), 'array') as $backend) {
if (!$backend || !is_string($backend)) {
Log::warning(
'Auth Init: Invalid auth backend retreive from configuration, ignore it: %s',
vardump($backend));
continue;
}
$class = (
$backend[0] == '\\'?
$backend:
"\\EesyPHP\\Auth\\".ucfirst($backend)
);
if (!class_exists($class)) {
Log::warning(
"Auth Init: Unknown auth backend '%s' retreived from configuration, ignore it",
$backend
);
continue;
}
$parents = class_parents($class);
if (!is_array($parents) || !in_array('EesyPHP\\Auth\\Backend', $parents)) {
Log::warning(
'Auth Init: Auth backend %s class (%s) do not derivate from \\EesyPHP\\Auth\\Backend '.
'class, ignore it.',
$backend, $class);
continue;
}
if (!call_user_func(array($class, 'init'))) {
Log::warning(
'Auth Init: fail to initialize auth backend %s, ignore it',
$backend, $class);
continue;
}
Log::trace('Auth backend %s initialized (class %s)', $backend, $class);
self :: $backends[strtolower($backend)] = $class;
}
}
/**
* Check if authentication is enabled
* @return bool
*/
public static function enabled() {
if (!is_null(App::get('auth.enabled', null, 'bool')))
return App::get('auth.enabled', false, 'bool');
if (App::get('auth.methods', array(), 'array') && App::get('auth.backends', array(), 'array'))
return true;
Log :: trace('Authentication is disabled');
return false;
}
/**
* Check if a authentification method is enabled
* @param string $method
* @return bool
*/
public static function method_is_enabled($method) {
return array_key_exists(strtolower($method), self :: $methods);
}
/**
* Get user by username
* @param string $username
* @param boolean $first Return only the first matched user (if authorized, optional, default: true)
* @return \EesyPHP\Auth\User|array<\EesyPHP\Auth\User>|null|false The user object if found, null it not, false in case of error
*/
public static function get_user($username, $first=true) {
if (!self :: $backends) {
Log :: warning("No auth backend registered, can't retreive user");
return false;
}
$users = array();
foreach (self :: $backends as $backend) {
$user = call_user_func(array($backend, 'get_user'), $username);
if ($user) $users[] = $user;
}
if (!$users) return null;
if (count($users) > 1 && !App::get('auth.allow_multiple_match', false, 'bool')) {
Log :: error('Multiple user found for username "%s":\n%s', $username, vardump($users));
return false;
}
return $first?$users[0]:$users;
}
/**
* Search user by username and check its password
* @param string $username The username
* @param string $password The password to check
* @return \EesyPHP\Auth\User|null|false
*/
public static function authenticate($username, $password) {
$users = self :: get_user($username, false);
if (!$users) return $users === false?false:null;
$auth_users = array();
foreach ($users as $user) {
if ($user->check_password($password))
$auth_users[] = $user;
}
if (!$auth_users) return null;
if (
count($auth_users) > 1
&& !App::get(
'auth.allow_multiple_match_with_valid_password',
App::get('auth.allow_multiple_match', false, 'bool'),
'bool'
)
) {
Log :: error(
'Multiple user match for username "%s" and provided password:\n%s',
$username, vardump($auth_users));
return false;
}
return $auth_users[0];
}
/**
* Log user
* @param bool|string $force Force user authentication: could specified desired method or true
* to use the first one (optional, default: false)
* @param null|string $with_method Specify with which method(s) login user (optional, default: all methods)
* @return \EesyPHP\Auth\User|null|false
*/
public static function login($force=false, $with_method=null) {
// Check if already logged in
if (self :: $user)
return self :: $user;
// Check if logged in session
if (isset($_SESSION['user']) && isset($_SESSION['auth_method'])) {
$user = unserialize($_SESSION['user']);
if (is_a($user, '\\EesyPHP\\Auth\\User')) {
self :: $user = $user;
self :: $logged_method = (
array_key_exists($_SESSION['auth_method'], self :: $methods)?
$_SESSION['auth_method']:null
);
Log :: debug(
'User %s authenticated from session (method %s and backend %s)',
$user->username,
self :: $logged_method?self :: $logged_method:'unknown',
$user->backend);
return $user;
}
Log::warning('Invalid user data in session, drop it');
// Otherwise, drop user in session
unset($_SESSION['user']);
unset($_SESSION['auth_method']);
}
if (!self :: $methods) {
Log :: warning("No auth method registered, can't authenticate users");
return false;
}
// Otherwise, log without enforcing by using registered methods
foreach (self :: $methods as $method => $class) {
if ($with_method && !in_array($method, array_map('strtolower', ensure_is_array($with_method))))
continue;
$user = call_user_func(array($class, 'login'));
if ($user) {
self :: set_user($user, $method);
return $user;
}
}
// If still not logged and force mode enable, force login using specified method (or the first one)
if ($force) {
$method = (
is_string($force) && array_key_exists($force, self :: $methods)?
$force:key(self :: $methods)
);
Log::debug('Force authentication using method %s', $method);
$user = call_user_func(array(self :: $methods[$method], 'login'), true);
if ($user) {
self :: set_user($user, $method);
return $user;
}
return false;
}
return null;
}
/**
* Helper to set current authenticated user
* @param \EesyPHP\Auth\User $user The current authenticated user object
* @param string $method Method used to authenticate the user
* @return void
*/
private static function set_user($user, $method) {
Log :: debug(
'User %s authenticated using method %s and backend %s',
$user->username, $method, $user->backend);
self :: $user = $user;
self :: $logged_method = $method;
$_SESSION['user'] = serialize($user);
$_SESSION['auth_method'] = $method;
Hook :: trigger('logged_in', array('method' => $method, 'user' => $user));
}
/**
* Logout
* @return void
*/
public static function logout() {
$method = (
self :: $logged_method?
self :: $logged_method:
(isset($_SESSION['auth_method'])?$_SESSION['auth_method']:null)
);
self :: $user = null;
self :: $logged_method = null;
if (isset($_SESSION['user']))
unset($_SESSION['user']);
if (isset($_SESSION['auth_method']))
unset($_SESSION['auth_method']);
if ($method)
call_user_func(array(self :: $methods[$method], 'logout'));
}
/**
* Get current authenticated user
* @return \EesyPHP\Auth\User|null
*/
public static function user() {
return self :: $user;
}
}