*/ private static $methods = array(); /** * Method name used to authenticate current user * @var string|null */ private static $logged_method = null; /** * Initialized backends * @var array */ private static $backends = array(); /** * Current authenticated user * @var \EesyPHP\Auth\User|null */ private static $user = null; /** * Initialize * @return void */ public static function init() { // 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, ) ); self :: $methods = array(); foreach(App::get('auth.methods', array(), 'array') as $method) { if (!$method || !is_string($method)) { Log::warning( 'Auth Init: Invalid auth method retrieve 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' retrieved 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 retrieve 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' retrieved 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 authentication 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 retrieve 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); Hook :: trigger( 'logged_in_from_session', array('method' => self :: $logged_method, 'user' => $user)); 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|null $method Method used to authenticate the user. If not null, user information * will be changed in session. * @return void */ 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 Log :: debug( 'User %s authenticated using method %s and backend %s', $user, $method, $user->backend); 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() { $user = self :: $user; $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']); Hook :: trigger('logout', array('method' => $method, 'user' => $user)); 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; } /** * Helper function to trigger access denied error * @param string|null $details Reason or details about this error * @return never */ public static function access_denied($details=null) { if (Tpl::initialized()) { Tpl :: assign("details", $details); Tpl :: display("access_denied.tpl", I18n::_("Access denied")); } else { printf("

%s

", I18n::_("Access denied")); printf("

%s

", I18n::_("You do not have access to this application.")); if ($details) printf( "

%s%s

", I18n::_("Details:"), $details ); } exit(); } }