Protéger une route¶
Objectif : refuser l'accès à une route si les rôles n'accordent pas la permission
requise.
Ce que vous allez apprendre : require_contract_permission(result, roles, permission)
renvoie une réponse 403 si la permission manque, sinon None (la route continue).
Une ligne en tête de contrôleur suffit.
Deuxième palier du niveau intermédiaire de la progression RBAC.
Module opt-in
Ce starter suppose forge-mvc-rbac installé et livre un contrat de démonstration.
Ce que ce starter montre¶
require_contract_permissioncomme garde ;- une réponse
403si refusée, la ressource sinon ; - la garde déclarative en tête de contrôleur.
Classes Forge utilisées¶
| Classe / fonction | Rôle dans ce starter | Référence |
|---|---|---|
forge_mvc_rbac.require_contract_permission |
Garde de route (403 si refusée, None sinon). | RBAC |
Tester¶
Ouvrez https://localhost:8000/rbac-guard?roles=reader (403) puis ?roles=editor
(autorisé).
Le contrôleur¶
Créez le contrôleur mvc/controllers/rbac_guard_controller.py :
# mvc/controllers/rbac_guard_controller.py
from core.http.request import Request
from core.http.response import Response
from core.mvc.controller.base_controller import BaseController
from forge_mvc_rbac import load_rbac_contract, require_contract_permission
_REQUIRED = "article.create"
class RbacGuardController(BaseController):
"""Starter pédagogique : protéger une route par une permission contractuelle."""
@staticmethod
def index(request: Request) -> Response:
roles_raw = request.query("roles") or "reader"
roles = [r.strip() for r in roles_raw.split(",") if r.strip()]
result = load_rbac_contract(".")
context = {"roles": roles_raw, "required": _REQUIRED}
denied = require_contract_permission(result, roles, _REQUIRED)
if denied is not None:
context["denied"] = True
return BaseController.render("rbac_guard/index.html", context=context, request=request, status=403)
context["allowed"] = True
return BaseController.render("rbac_guard/index.html", context=context, request=request)
Comprendre ce code¶
- Le pattern est explicite : on appelle la garde, et si elle renvoie une réponse,
on la retourne immédiatement (court-circuit403). - En production, les rôles viennent de l'utilisateur connecté, pas de l'URL ;
ici on les passe en paramètre pour la démonstration. - La permission requise est déclarée une fois, en tête de l'action.
Le contrat¶
Ce palier réutilise le contrat mvc/security/rbac.json introduit au palier
« Bonjour Forge RBAC ». Si vous démarrez ici, créez-le :
{
"schema_version": "1.0",
"entities": {
"Article": {
"permissions": {
"list": "article.list",
"show": "article.show",
"create": "article.create",
"update": "article.update",
"delete": "article.delete"
}
}
},
"roles": {
"admin": ["article.list", "article.show", "article.create", "article.update", "article.delete"],
"editor": ["article.list", "article.show", "article.create", "article.update"],
"reader": ["article.list", "article.show"]
}
}
La vue¶
Créez la vue mvc/views/rbac_guard/index.html :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Protéger une route - Forge</title>
</head>
<body>
<h1>Protéger une route</h1>
<p>Ressource protégée - permission requise : <code>{{ required }}</code></p>
<p>Rôles présentés : <code>{{ roles }}</code></p>
{% if allowed %}
<p data-level="success">✓ Accès autorisé : la route continue.</p>
{% endif %}
{% if denied %}
<p data-level="error">✗ 403 - permission refusée pour ces rôles.</p>
{% endif %}
<p>Essayez <code>?roles=reader</code> (403) puis <code>?roles=editor</code> (autorisé).
En production, les rôles viennent de l'utilisateur connecté, pas de l'URL.</p>
</body>
</html>
La route¶
Ajoutez l'import et la route dans le groupe public de mvc/routes.py :
# mvc/routes.py
from mvc.controllers.rbac_guard_controller import RbacGuardController
with router.group("", public=True) as public:
public.add("GET", "/rbac-guard", RbacGuardController.index, name="rbac_guard_index")
À retenir¶
require_contract_permission= garde de route,403ou passage.- Court-circuit explicite :
if denied is not None: return denied. - Les rôles réels proviennent de la session/utilisateur.
Après ce starter¶
La suite : adapter l'interface aux permissions.