2023-02-25 05:02:27 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace EesyPHP;
|
|
|
|
|
2023-02-28 16:51:32 +01:00
|
|
|
use EesyPHP\Auth\User;
|
|
|
|
|
2023-02-25 05:02:27 +01:00
|
|
|
|
|
|
|
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() {
|
2023-03-01 16:22:11 +01:00
|
|
|
// Set config default values
|
|
|
|
App :: set_default(
|
|
|
|
'auth',
|
|
|
|
array(
|
|
|
|
'methods' => array(),
|
|
|
|
'backends' => array(),
|
|
|
|
'enabled' => self :: enabled(),
|
|
|
|
'allow_multiple_match' => null,
|
|
|
|
'allow_multiple_match_with_valid_password' => null,
|
|
|
|
)
|
|
|
|
);
|
2023-02-25 05:02:27 +01:00
|
|
|
self :: $methods = array();
|
|
|
|
foreach(App::get('auth.methods', array(), 'array') as $method) {
|
|
|
|
if (!$method || !is_string($method)) {
|
|
|
|
Log::warning(
|
2024-01-23 19:23:10 +01:00
|
|
|
'Auth Init: Invalid auth method retrieve from configuration, ignore it: %s',
|
2023-02-25 05:02:27 +01:00
|
|
|
vardump($method));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$class = (
|
|
|
|
$method[0] == '\\'?
|
|
|
|
$method:
|
|
|
|
"\\EesyPHP\\Auth\\".ucfirst($method)
|
|
|
|
);
|
|
|
|
if (!class_exists($class)) {
|
|
|
|
Log::warning(
|
2024-01-23 19:23:10 +01:00
|
|
|
"Auth Init: Unknown auth method '%s' retrieved from configuration, ignore it",
|
2023-02-25 05:02:27 +01:00
|
|
|
$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(
|
2024-01-23 19:23:10 +01:00
|
|
|
'Auth Init: Invalid auth backend retrieve from configuration, ignore it: %s',
|
2023-02-25 05:02:27 +01:00
|
|
|
vardump($backend));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$class = (
|
|
|
|
$backend[0] == '\\'?
|
|
|
|
$backend:
|
|
|
|
"\\EesyPHP\\Auth\\".ucfirst($backend)
|
|
|
|
);
|
|
|
|
if (!class_exists($class)) {
|
|
|
|
Log::warning(
|
2024-01-23 19:23:10 +01:00
|
|
|
"Auth Init: Unknown auth backend '%s' retrieved from configuration, ignore it",
|
2023-02-25 05:02:27 +01:00
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-01-23 19:23:10 +01:00
|
|
|
* Check if a authentication method is enabled
|
2023-02-25 05:02:27 +01:00
|
|
|
* @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) {
|
2024-01-23 19:23:10 +01:00
|
|
|
Log :: warning("No auth backend registered, can't retrieve user");
|
2023-02-25 05:02:27 +01:00
|
|
|
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);
|
2023-02-27 13:22:53 +01:00
|
|
|
Hook :: trigger(
|
|
|
|
'logged_in_from_session', array('method' => self :: $logged_method, 'user' => $user));
|
2023-02-25 05:02:27 +01:00
|
|
|
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
|
2023-02-28 16:51:32 +01:00
|
|
|
* @param string|null $method Method used to authenticate the user. If not null, user information
|
|
|
|
* will be changed in session.
|
2023-02-25 05:02:27 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2023-02-28 16:51:32 +01:00
|
|
|
public static function set_user($user, $method=null) {
|
|
|
|
if (!$user instanceof User)
|
|
|
|
throw new \Exception(
|
|
|
|
"Provided user is not an instance of \\EesyPHP\\Auth\\User: ".vardump($user)
|
|
|
|
);
|
|
|
|
self :: $user = $user;
|
|
|
|
|
|
|
|
if (!$method) return;
|
|
|
|
|
|
|
|
// Method provided: update user info in session and trigger hook
|
2023-02-25 05:02:27 +01:00
|
|
|
Log :: debug(
|
|
|
|
'User %s authenticated using method %s and backend %s',
|
2023-02-28 16:51:32 +01:00
|
|
|
$user, $method, $user->backend);
|
2023-02-25 05:02:27 +01:00
|
|
|
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() {
|
2023-02-25 15:49:47 +01:00
|
|
|
$user = self :: $user;
|
2023-02-25 05:02:27 +01:00
|
|
|
$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']);
|
2023-02-25 15:49:47 +01:00
|
|
|
Hook :: trigger('logout', array('method' => $method, 'user' => $user));
|
2023-02-25 05:02:27 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|