2023-12-03 22:10:18 +04:00
|
|
|
<?php
|
|
|
|
namespace nur\b\authnz;
|
|
|
|
|
|
|
|
use nur\b\IllegalAccessException;
|
|
|
|
use nur\b\ValueException;
|
|
|
|
use nur\config;
|
|
|
|
use nur\cookie;
|
|
|
|
use nur\session;
|
|
|
|
use nur\v\page;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Interface IAuthzManager: gestionnaire d'authentification et d'autorisation
|
|
|
|
*/
|
|
|
|
class AuthzManager {
|
|
|
|
#############################################################################
|
|
|
|
# Auth
|
|
|
|
|
|
|
|
const COOKIE_KEY_AUTH = "auth:app:";
|
|
|
|
/** durée du cookie d'authentification: 17 jours par défaut */
|
|
|
|
const COOKIE_DURATION = 17 * 24 * 60;
|
|
|
|
|
|
|
|
const SESSION_KEY_COOKIE = "authz:cookie";
|
|
|
|
const SESSION_KEY_STATUS = "authz:status";
|
|
|
|
const SESSION_KEY_USERNAME = "authz:username";
|
|
|
|
const SESSION_KEY_USER = "authz:user";
|
|
|
|
const SESSION_KEY_SULOGIN = "authz:sulogin";
|
|
|
|
|
|
|
|
/** redirection vers la page de login pour une connexion standard */
|
|
|
|
const REASON_LOGIN = 1;
|
|
|
|
/** redirection vers la page de login pour cause de session expirée */
|
|
|
|
const REASON_SESSION = 2;
|
|
|
|
/** redirection vers la page de login pour accès interdit */
|
|
|
|
const REASON_UNAUTHORIZED = 3;
|
|
|
|
|
|
|
|
/** pas de statut en particulier */
|
|
|
|
const STATUS_NONE = 0;
|
|
|
|
/** session fraichement démarrée */
|
|
|
|
const STATUS_INITIAL = 1;
|
|
|
|
/** session déconnecté */
|
|
|
|
const STATUS_DISCONNECTED = 2;
|
|
|
|
/** accès non autorisé */
|
|
|
|
const STATUS_UNAUTHORIZED = 3;
|
|
|
|
|
|
|
|
protected function getCookieKey(): string {
|
|
|
|
return self::COOKIE_KEY_AUTH.config::get_appcode();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getCookieDuration(): int {
|
|
|
|
return static::COOKIE_DURATION;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** vérifier que le cookie d'authentification est posé et valide. */
|
|
|
|
function checkCookie(?string &$username=null, ?string &$authType=null): bool {
|
|
|
|
$value = cookie::get($this->getCookieKey(), false);
|
|
|
|
if ($value === false) return false;
|
|
|
|
if (!preg_match('/^(cas|form|):(.*)$/', $value, $ms)) return false;
|
|
|
|
$authType = $ms[1];
|
|
|
|
$username = $ms[2];
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function setCookie(string $username, string $authType): void {
|
|
|
|
cookie::setd($this->getCookieKey(), "$authType:$username", $this->getCookieDuration());
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function resetCookie(): void {
|
|
|
|
cookie::set($this->getCookieKey(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
# Session
|
|
|
|
|
|
|
|
/** @var int */
|
|
|
|
private $status = self::STATUS_NONE;
|
|
|
|
|
|
|
|
function getStatus(): int {
|
|
|
|
return $this->status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-06-25 23:01:48 +04:00
|
|
|
* Vérifier que la session est valide.
|
2023-12-03 22:10:18 +04:00
|
|
|
*
|
|
|
|
* comme raccourci, si la session est valide, initialiser $username avec le
|
|
|
|
* nom d'utilisateur *du cookie*
|
|
|
|
*/
|
|
|
|
function checkSession(?string &$username=null, ?string &$authType=null): bool {
|
|
|
|
if (!$this->checkCookie($username, $authType)) return false;
|
|
|
|
session::start();
|
|
|
|
$this->status = session::get(self::SESSION_KEY_STATUS, self::STATUS_NONE);
|
|
|
|
return session::get(self::SESSION_KEY_COOKIE) === "$authType:$username";
|
|
|
|
}
|
|
|
|
|
|
|
|
function isNewSession(): bool {
|
|
|
|
return session::get(self::SESSION_KEY_COOKIE) === null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function initSession(int $status): void {
|
|
|
|
$this->checkCookie($username, $authType);
|
|
|
|
session::start();
|
|
|
|
session::set(self::SESSION_KEY_COOKIE, "$authType:$username");
|
|
|
|
$this->status = $status;
|
|
|
|
session::set(self::SESSION_KEY_STATUS, $status);
|
|
|
|
session::set(self::SESSION_KEY_USERNAME, null);
|
|
|
|
session::set(self::SESSION_KEY_USER, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
function resetSession(int $status=self::STATUS_DISCONNECTED): void {
|
|
|
|
if ($this->checkCookie()) {
|
|
|
|
session::start();
|
|
|
|
session::destroy(true);
|
|
|
|
$this->initSession($status);
|
|
|
|
} elseif (session::started()) {
|
|
|
|
session::destroy(true);
|
|
|
|
$this->initSession($status);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
private $auth;
|
|
|
|
|
|
|
|
/** tester si l'utilisateur est connecté */
|
|
|
|
function _isAuth(): bool {
|
2024-06-25 23:01:48 +04:00
|
|
|
if (!session::started() && session::started_once()) {
|
|
|
|
# la session a déjà été démarrée, pas besoin de la démarrer de nouveau
|
|
|
|
} else if (!$this->checkSession()) {
|
|
|
|
return false;
|
|
|
|
}
|
2023-12-03 22:10:18 +04:00
|
|
|
return session::get(self::SESSION_KEY_USERNAME) !== null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* obtenir le compte de l'utilisateur connecté
|
|
|
|
*
|
|
|
|
* @throws ValueException si aucun utilisateur n'est connecté
|
|
|
|
*/
|
|
|
|
function _getAuth(): string {
|
2024-06-25 23:01:48 +04:00
|
|
|
if (!session::started() && session::started_once()) {
|
|
|
|
# la session a déjà été démarrée, pas besoin de la démarrer de nouveau
|
|
|
|
$username = session::get(self::SESSION_KEY_USERNAME);
|
|
|
|
if ($username !== null) return $username;
|
|
|
|
} elseif ($this->checkSession()) {
|
2023-12-03 22:10:18 +04:00
|
|
|
$username = session::get(self::SESSION_KEY_USERNAME);
|
|
|
|
if ($username !== null) return $username;
|
|
|
|
}
|
|
|
|
throw new ValueException("not authenticated");
|
|
|
|
}
|
|
|
|
|
|
|
|
function isAuth(): bool {
|
|
|
|
$auth = $this->auth;
|
|
|
|
if ($auth !== null) return true;
|
|
|
|
else return $this->_isAuth();
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAuth(): string {
|
|
|
|
$auth = $this->auth;
|
|
|
|
if ($auth !== null) return $auth;
|
|
|
|
else return $this->_getAuth();
|
|
|
|
}
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
# Authz
|
|
|
|
|
|
|
|
const USER_MANAGER_CLASS = SimpleUserManager::class;
|
|
|
|
|
|
|
|
/** @var IUserManager */
|
|
|
|
private static $user_manager;
|
|
|
|
|
|
|
|
protected function getUserManager(): IUserManager {
|
|
|
|
if (self::$user_manager === null) {
|
|
|
|
$class = static::USER_MANAGER_CLASS;
|
|
|
|
self::$user_manager = new $class();
|
|
|
|
}
|
|
|
|
return self::$user_manager;
|
|
|
|
}
|
|
|
|
|
|
|
|
function redirect(int $reason, string $destUrl, string $loginUrl): void {
|
|
|
|
$params = ["d" => $destUrl];
|
|
|
|
switch ($reason) {
|
|
|
|
case self::REASON_LOGIN:
|
|
|
|
$params["a"] = 1; // autologin si possible
|
|
|
|
break;
|
|
|
|
case self::REASON_SESSION:
|
|
|
|
$this->resetSession(self::STATUS_DISCONNECTED);
|
|
|
|
break;
|
|
|
|
case self::REASON_UNAUTHORIZED:
|
|
|
|
session::set(self::SESSION_KEY_STATUS, self::STATUS_UNAUTHORIZED);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw IllegalAccessException::unexpected_state();
|
|
|
|
}
|
|
|
|
page::redirect(page::bu($loginUrl, $params));
|
|
|
|
}
|
|
|
|
|
|
|
|
function formLogin(string $username, string $password): bool {
|
|
|
|
# l'utilisateur doit exister
|
|
|
|
$user = $this->getUserManager()->getAuthzUser($username, null);
|
|
|
|
if ($user !== null) {
|
|
|
|
# ce doit être un utilisateur valide
|
|
|
|
if ($user->isValid()) {
|
|
|
|
$this->setCookie($username, "form");
|
|
|
|
# le cas échéant le mot de passe doit correspondre
|
|
|
|
if (config::is_devel() && !$password) $password = null;
|
|
|
|
if ($password === null || $user->validatePassword($password)) {
|
|
|
|
# c'est bon
|
|
|
|
$this->initSession(self::STATUS_INITIAL, null);
|
|
|
|
session::set(self::SESSION_KEY_USERNAME, $username);
|
|
|
|
$this->onAuthOk($username);
|
|
|
|
session::set(self::SESSION_KEY_USER, $user);
|
|
|
|
$this->onAuthzOk($user);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function casLogin(string $username, ?array $overrides): bool {
|
|
|
|
$this->setCookie($username, "cas");
|
|
|
|
$this->initSession(self::STATUS_INITIAL);
|
|
|
|
session::set(self::SESSION_KEY_USERNAME, $username);
|
|
|
|
$this->onAuthOk($username);
|
|
|
|
# l'utilisateur doit exister
|
|
|
|
$user = $this->getUserManager()->getAuthzUser($username, $overrides);
|
|
|
|
if ($user !== null) {
|
|
|
|
# ce doit être un utilisteur valide
|
|
|
|
if ($user->isValid()) {
|
|
|
|
# c'est bon
|
|
|
|
session::set(self::SESSION_KEY_USER, $user);
|
|
|
|
$this->onAuthzOk($user);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* après les avoir chargées le cas échéant, retourner les informations
|
|
|
|
* d'autorisation de l'utilisateur spécifié ou une instance invalide s'il n'y
|
|
|
|
* en a pas. mettre à jour la session le cas échéant
|
|
|
|
*
|
|
|
|
* prendre par défaut l'utilisateur connecté
|
|
|
|
*/
|
|
|
|
function _selectAuthz(?string $username=null): IAuthzUser {
|
|
|
|
# ici, la session a peut-être déjà été fermée, mais si elle a été ouverte au
|
|
|
|
# moins une fois, en tenir compte quand on accède aux informations
|
|
|
|
if ($username === null) {
|
|
|
|
if (!session::started_once() || !$this->isAuth()) return InvalidUser::with();
|
|
|
|
$username = $this->getAuth();
|
|
|
|
$user = session::get(self::SESSION_KEY_USER);
|
|
|
|
} else if (session::started_once() && $this->isAuth()) {
|
|
|
|
$user = session::get(self::SESSION_KEY_USER);
|
|
|
|
} else {
|
|
|
|
$user = null;
|
|
|
|
}
|
|
|
|
if ($user instanceof IAuthzUser && $user->getUsername() === $username) {
|
|
|
|
return $user;
|
|
|
|
}
|
|
|
|
$user = $this->getUserManager()->getAuthzUser($username, null);
|
|
|
|
if ($user === null) $user = InvalidUser::with($username);
|
|
|
|
session::set(self::SESSION_KEY_USERNAME, $username);
|
|
|
|
session::set(self::SESSION_KEY_USER, $user);
|
|
|
|
return $user;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var IAuthzUser */
|
|
|
|
private $authz;
|
|
|
|
|
|
|
|
function setConnected(): void {
|
|
|
|
$this->auth = $this->_getAuth();
|
|
|
|
$this->authz = $this->_selectAuthz();
|
|
|
|
}
|
|
|
|
|
|
|
|
function selectAuthz(?string $username=null): IAuthzUser {
|
|
|
|
$authz = null;
|
|
|
|
if ($username === null) $authz = $this->authz;
|
|
|
|
if ($authz === null) $authz = $this->_selectAuthz($username);
|
|
|
|
return $authz;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** obtenir les informations d'autorisation de l'utilisateur effectif */
|
|
|
|
function getUser(): IAuthzUser {
|
|
|
|
#XXX le cas échéant, ajouter les informations de l'utilisateur effectif
|
|
|
|
return $this->selectAuthz();
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkAuthz(?array $roles, ?array $perms): bool {
|
|
|
|
$authz = $this->selectAuthz();
|
|
|
|
if ($roles !== null && !$authz->isRole($roles)) return false;
|
|
|
|
if ($perms !== null && !$authz->isPerm($perms)) return false;
|
|
|
|
# sinon, au moins un rôle ou une permission doivent être définis
|
|
|
|
return $authz->isRole(IAuthzUser::ROLE_AUTHZ);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** la connexion actuelle a-t-elle été forcée par un administrateur? */
|
|
|
|
function isSulogin(): bool {
|
|
|
|
return boolval(session::get(self::SESSION_KEY_SULOGIN, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** marquer la connexion actuelle comme étant faite par un administrateur. */
|
|
|
|
function setSulogin(bool $sulogin=true): void {
|
|
|
|
if ($sulogin) {
|
|
|
|
$user = session::get(self::SESSION_KEY_SULOGIN, false);
|
|
|
|
if ($user === false) $user = session::get(self::SESSION_KEY_USER);
|
|
|
|
session::set(self::SESSION_KEY_SULOGIN, $user);
|
|
|
|
} else {
|
|
|
|
session::set(self::SESSION_KEY_SULOGIN, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
# Méthodes surchargeables
|
|
|
|
|
|
|
|
/** Traiter le cas où l'utilisateur s'est authentifié avec succès. */
|
|
|
|
function onAuthOk(string $username): void {
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Traiter le cas où l'utilisateur a été autorisé avec succès. */
|
|
|
|
function onAuthzOk(IAuthzUser $authz): void {
|
|
|
|
}
|
|
|
|
}
|