transfer from private server/git server
This commit is contained in:
83
web/services/auth_service.py
Normal file
83
web/services/auth_service.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
auth_service.py — Fonctions de validation des droits d'accès, centralisées et
|
||||
réutilisées par toutes les routes (Bloc 1 - C9 : taux de réutilisation > 80 %
|
||||
via services/ ; Bloc 1 - C8 : "vérification JWT à chaque requête").
|
||||
|
||||
Toute route sensible doit passer par current_user() / require_login() /
|
||||
require_admin() plutôt que de relire directement flask.session, afin que la
|
||||
règle d'autorisation soit définie à un seul endroit.
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import session, jsonify, request
|
||||
|
||||
from config import Config
|
||||
|
||||
|
||||
def current_user():
|
||||
"""Retourne l'utilisateur en session (dict issu du token OIDC) ou None."""
|
||||
return session.get("user")
|
||||
|
||||
|
||||
def is_authenticated():
|
||||
return current_user() is not None
|
||||
|
||||
|
||||
_UNSET = object()
|
||||
|
||||
|
||||
def is_admin(user=_UNSET):
|
||||
"""True si l'utilisateur appartient au groupe Keycloak /admin.
|
||||
|
||||
Appelé sans argument, lit l'utilisateur courant dans la session Flask
|
||||
(nécessite un contexte de requête actif). Appelé avec un argument
|
||||
explicite (y compris None), ce dict/None est utilisé tel quel — utile
|
||||
pour tester la fonction hors contexte de requête (cf. UT-11).
|
||||
"""
|
||||
if user is _UNSET:
|
||||
user = current_user()
|
||||
if not user:
|
||||
return False
|
||||
return Config.ADMIN_GROUP in user.get("groups", [])
|
||||
|
||||
|
||||
def require_login(view_func):
|
||||
"""Décorateur : bloque l'accès si aucune session utilisateur valide."""
|
||||
|
||||
@wraps(view_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not is_authenticated():
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
return view_func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def require_admin(view_func):
|
||||
"""Décorateur : bloque l'accès si l'utilisateur n'a pas le groupe /admin.
|
||||
|
||||
Utilisé par toutes les routes de modification (annonces, fichiers,
|
||||
blocks). Aucune route admin n'est accessible sans token valide et sans
|
||||
appartenance au groupe Keycloak /admin (mitigation du risque "accès non
|
||||
autorisé à /admin", cf. tableau OWASP du dossier technique).
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
user = current_user()
|
||||
if not user:
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
if not is_admin(user):
|
||||
return jsonify({"error": "forbidden"}), 403
|
||||
return view_func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def client_ip():
|
||||
"""IP cliente, en tenant compte du proxy Caddy (X-Forwarded-For)."""
|
||||
forwarded = request.headers.get("X-Forwarded-For", "")
|
||||
if forwarded:
|
||||
return forwarded.split(",")[0].strip()
|
||||
return request.remote_addr
|
||||
Reference in New Issue
Block a user