423 lines
13 KiB
PHP
423 lines
13 KiB
PHP
<?php
|
|
namespace nur\b\authnz;
|
|
|
|
use nur\authz;
|
|
use nur\b\IllegalAccessException;
|
|
use nur\b\ValueException;
|
|
use nur\config;
|
|
use nur\cookie;
|
|
use nur\F;
|
|
use nur\msg;
|
|
use nur\P;
|
|
use nur\session;
|
|
use nur\v\fo;
|
|
use nur\v\ly;
|
|
use nur\v\page;
|
|
use nur\v\v;
|
|
use nur\v\vo;
|
|
use nur\v\vp\AInitAuthzPage;
|
|
use nur\v\vp\AInitPage;
|
|
|
|
/**
|
|
* 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|ext|):(.*)$/', $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 extLogin(string $username): bool {
|
|
# l'utilisateur doit exister
|
|
$user = $this->getUserManager()->getAuthzUser($username, null);
|
|
# ce doit être un utilisateur valide
|
|
if ($user !== null && $user->isValid()) {
|
|
$this->setCookie($username, "ext");
|
|
$this->initSession(self::STATUS_INITIAL);
|
|
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 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);
|
|
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);
|
|
# ce doit être un utilisteur valide
|
|
if ($user !== null && $user->isValid()) {
|
|
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 UnknownUser::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 = UnknownUser::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 {
|
|
}
|
|
|
|
#############################################################################
|
|
# Page login
|
|
|
|
private $username = null;
|
|
private $authType = null;
|
|
|
|
function beforeSetup(AInitAuthzPage $page): void {
|
|
# initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
|
|
# setup() et afterSetup(), la session est disponible
|
|
$username = P::get("u");
|
|
$password = P::get("p");
|
|
$destPage = F::get("d", $page->getMainUrl());
|
|
$page->_ensureFormLoginAndRedirect($username, $password, $destPage);
|
|
|
|
$this->checkSession($this->username, $this->authType);
|
|
}
|
|
|
|
function print(AInitAuthzPage $page): void {
|
|
page::no_cache();
|
|
$username = P::get("u");
|
|
$password = P::get("p");
|
|
|
|
ly::row();
|
|
vo::h1(["class" => "text-center", q($page->TITLE())]);
|
|
|
|
ly::col(["sm" => 6, "sm-push" => 3]);
|
|
$status = $this->getStatus();
|
|
switch ($status) {
|
|
case authz::DISCONNECTED:
|
|
msg::warning("Vous avez été déconnecté. Veuillez vous reconnecter");
|
|
break;
|
|
case authz::UNAUTHORIZED:
|
|
msg::error(["user" => [
|
|
"Connecté en tant que ",
|
|
v::b($this->getAuth()),
|
|
", vous n'êtes pas autorisé à accéder à la page que vous avez demandé.",
|
|
]]);
|
|
break;
|
|
}
|
|
|
|
ly::panel("Connexion par identifiant/mot de passe");
|
|
fo::start([
|
|
"type" => "basic",
|
|
"action" => "",
|
|
"method" => "post",
|
|
]);
|
|
fo::text("Identifiant", "u", $username?: $this->username, [
|
|
"accesskey" => "q",
|
|
"placeholder" => "Votre identifiant",
|
|
]);
|
|
fo::password("Mot de passe", "p", $password, [
|
|
"placeholder" => "Votre mot de passe",
|
|
]);
|
|
if ($username || $password) {
|
|
msg::error("$username: Votre identifiant et/ou votre mot de passe sont incorrects");
|
|
} elseif ($username === "") {
|
|
msg::error("Vous devez saisir votre identifiant");
|
|
} elseif ($password === "") {
|
|
msg::error("Vous devez saisir votre mot de passe");
|
|
}
|
|
fo::submit(["Connexion", "accesskey" => "r"]);
|
|
if ($this->isAuth() && $this->authType === "form") {
|
|
if ($status != authz::UNAUTHORIZED) {
|
|
msg::warning(["user" => [
|
|
"Connecté en tant que ",
|
|
v::b($this->getAuth()),
|
|
", vous n'êtes pas autorisé à accéder à cette application.",
|
|
]]);
|
|
}
|
|
fo::submit([
|
|
"Vous déconnecter", "accesskey" => "z",
|
|
"formmethod" => "get", "formaction" => $page->getLogoutUrl(),
|
|
]);
|
|
}
|
|
fo::end();
|
|
|
|
ly::end();
|
|
}
|
|
}
|