150 lines
5.8 KiB
Python
150 lines
5.8 KiB
Python
"""Contrôle d'accès centralisé de l'application.
|
|
|
|
La politique s'applique à toutes les routes enregistrées. Les décorateurs locaux
|
|
restent utiles pour documenter les cas particuliers, mais une route oubliée ne
|
|
retombe plus sur le simple fait d'être connecté.
|
|
"""
|
|
from functools import wraps
|
|
|
|
from flask import abort, request
|
|
from flask_login import current_user
|
|
|
|
|
|
ROLE_ALIASES = {
|
|
"super_admin": "admin",
|
|
"admin": "admin",
|
|
"chef": "responsable_gmao",
|
|
"responsable": "responsable_gmao",
|
|
"responsable_gmao": "responsable_gmao",
|
|
"tech": "technicien",
|
|
"technician": "technicien",
|
|
"technicien": "technicien",
|
|
"assistant_prevention": "assistant_prevention",
|
|
"requester": "demandeur",
|
|
"user": "demandeur",
|
|
"demandeur": "demandeur",
|
|
"viewer": "lecture",
|
|
"lecture": "lecture",
|
|
}
|
|
|
|
ROLE_LABELS = {
|
|
"admin": "Administrateur système",
|
|
"responsable_gmao": "Responsable GMAO",
|
|
"technicien": "Technicien",
|
|
"assistant_prevention": "Assistant de prévention",
|
|
"demandeur": "Demandeur",
|
|
"lecture": "Lecture seule",
|
|
}
|
|
|
|
PERMISSIONS_BY_ROLE = {
|
|
"admin": {"*"},
|
|
"responsable_gmao": {
|
|
"dashboard.view", "intervention.view", "intervention.create", "intervention.manage",
|
|
"patrimoine.view", "patrimoine.manage", "planning.view", "planning.manage",
|
|
"stock.view", "stock.manage", "contract.view", "contract.manage",
|
|
"prevention.view", "prevention.manage", "export.use",
|
|
},
|
|
"technicien": {
|
|
"dashboard.view", "intervention.view", "intervention.create", "intervention.manage",
|
|
"patrimoine.view", "patrimoine.manage", "planning.view", "planning.manage",
|
|
"stock.view", "stock.manage", "contract.view", "prevention.view", "export.use",
|
|
},
|
|
"assistant_prevention": {
|
|
"dashboard.view", "intervention.view", "intervention.create", "patrimoine.view",
|
|
"planning.view", "prevention.view", "prevention.manage", "export.use",
|
|
},
|
|
"demandeur": {"dashboard.view", "intervention.view", "intervention.create"},
|
|
"lecture": {"dashboard.view", "intervention.view", "patrimoine.view", "planning.view", "contract.view"},
|
|
}
|
|
|
|
PUBLIC_ENDPOINTS = {
|
|
"auth.login", "health.health", "static",
|
|
"setup_wizard.index", "setup_wizard.api_progress", "setup_wizard.api_save_step",
|
|
"setup_wizard.api_complete", "setup_wizard.api_test_ent", "setup_wizard.api_test_pronote",
|
|
}
|
|
|
|
ADMIN_BLUEPRINTS = {
|
|
"admin", "ai_config", "gmao_config", "logs", "status", "yeastar",
|
|
"outlook_auth", "outlook_dashboard", "outlook_pages", "outlook_sync", "ent", "pronote",
|
|
}
|
|
PATRIMOINE_BLUEPRINTS = {
|
|
"equipments", "equipments_meters", "equipments_documents", "equipments_restrictions", "housing",
|
|
"equipments_scheduled", "buildings", "zones", "rooms", "room_types", "wizard", "lots",
|
|
}
|
|
PLANNING_BLUEPRINTS = {"planning", "scheduler", "interventions_planning"}
|
|
STOCK_BLUEPRINTS = {"parts", "meters"}
|
|
CONTRACT_BLUEPRINTS = {"companies", "contracts", "services"}
|
|
PREVENTION_BLUEPRINTS = {"trainings", "constraints", "prevention"}
|
|
|
|
|
|
def canonical_role(role):
|
|
return ROLE_ALIASES.get((role or "").strip().lower(), "lecture")
|
|
|
|
|
|
def has_permission(permission, user=None):
|
|
user = user or current_user
|
|
if not getattr(user, "is_authenticated", False):
|
|
return False
|
|
permissions = PERMISSIONS_BY_ROLE.get(canonical_role(user.role), set())
|
|
return "*" in permissions or permission in permissions
|
|
|
|
|
|
def permission_required(permission):
|
|
def decorator(view):
|
|
@wraps(view)
|
|
def wrapped(*args, **kwargs):
|
|
if not has_permission(permission):
|
|
abort(403)
|
|
return view(*args, **kwargs)
|
|
return wrapped
|
|
return decorator
|
|
|
|
|
|
def required_permission(endpoint, method):
|
|
"""Déduit la permission minimale d'une route enregistrée."""
|
|
if not endpoint:
|
|
return None
|
|
blueprint = endpoint.split(".", 1)[0]
|
|
mutating = method not in {"GET", "HEAD", "OPTIONS"}
|
|
|
|
if blueprint in ADMIN_BLUEPRINTS:
|
|
return "system.admin"
|
|
if blueprint == "auth":
|
|
return "system.admin" if endpoint not in {"auth.profile", "auth.change_own_password", "auth.logout"} else None
|
|
if blueprint == "setup_wizard":
|
|
return "system.admin"
|
|
if blueprint in PATRIMOINE_BLUEPRINTS:
|
|
return "patrimoine.manage" if mutating else "patrimoine.view"
|
|
if blueprint in PLANNING_BLUEPRINTS:
|
|
return "planning.manage" if mutating else "planning.view"
|
|
if blueprint == "interventions":
|
|
if endpoint in {"interventions.create", "interventions.create_for_group"}:
|
|
return "intervention.create"
|
|
return "intervention.manage" if mutating else "intervention.view"
|
|
if blueprint in STOCK_BLUEPRINTS:
|
|
return "stock.manage" if mutating else "stock.view"
|
|
if blueprint in CONTRACT_BLUEPRINTS:
|
|
return "contract.manage" if mutating else "contract.view"
|
|
if blueprint in PREVENTION_BLUEPRINTS:
|
|
return "prevention.manage" if mutating else "prevention.view"
|
|
if blueprint == "exports":
|
|
return "export.use"
|
|
if blueprint in {"dashboard", "notifications", "messagerie", "chatbot"}:
|
|
return "dashboard.view"
|
|
return "system.admin" if mutating else "dashboard.view"
|
|
|
|
|
|
def enforce_route_permission():
|
|
"""Garde global appelé avant chaque requête."""
|
|
endpoint = request.endpoint
|
|
if endpoint in PUBLIC_ENDPOINTS or endpoint == "static":
|
|
return None
|
|
# L'API REST applique sa clé ou sa session dans son propre décorateur.
|
|
if endpoint and endpoint.startswith("api_v1."):
|
|
return None
|
|
if not current_user.is_authenticated:
|
|
return None # Flask-Login conserve la redirection habituelle des vues protégées.
|
|
permission = required_permission(endpoint, request.method)
|
|
if permission and not has_permission(permission):
|
|
abort(403)
|
|
return None
|