nur-sery/nur_src/b/authnz/AuthzManager.php

321 lines
10 KiB
PHP

<?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;
}
/**
* Vérifier que la session est valide.
*
* 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 {
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;
}
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 {
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()) {
$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 {
}
}