feat: publish CodexPi contest snapshot
Some checks are pending
Qualité / tests (push) Waiting to run
Some checks are pending
Qualité / tests (push) Waiting to run
This commit is contained in:
104
django_ui/identity/views.py
Normal file
104
django_ui/identity/views.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Same-origin identity contract in the existing Django service, deny by default."""
|
||||
from functools import wraps
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import DatabaseError
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_GET, require_POST, require_http_methods
|
||||
|
||||
from django_ui.views import AUDIT, client_ip, json_body, security_headers
|
||||
from .models import APPLICATIONS, ROLES, ApplicationGrant
|
||||
|
||||
PERMISSIONS = {"reader": ("read",), "operator": ("read", "write"), "admin": ("read", "write", "admin")}
|
||||
|
||||
|
||||
def reply(body, status=200):
|
||||
response = security_headers(JsonResponse(body, status=status))
|
||||
response["Vary"] = "Cookie"
|
||||
return response
|
||||
|
||||
|
||||
def identity_request(view):
|
||||
@wraps(view)
|
||||
def guarded(request, application, **kwargs):
|
||||
try:
|
||||
if not request.user.is_authenticated:
|
||||
return reply({"detail": "Authentification requise"}, 401)
|
||||
if application not in APPLICATIONS:
|
||||
return reply({"detail": "Application inconnue"}, 404)
|
||||
if request.headers.get("Sec-Fetch-Site") == "cross-site":
|
||||
return reply({"detail": "Requête intersite refusée"}, 403)
|
||||
if request.method not in {"GET", "HEAD", "OPTIONS"} and (
|
||||
request.content_type != "application/json" or len(request.body) > 4096
|
||||
):
|
||||
return reply({"detail": "Corps JSON limité requis"}, 400)
|
||||
return view(request, application, **kwargs)
|
||||
except DatabaseError:
|
||||
return reply({"detail": "Identité indisponible ; accès refusé"}, 503)
|
||||
|
||||
return guarded
|
||||
|
||||
|
||||
def principal(request, application):
|
||||
grant = ApplicationGrant.objects.filter(user=request.user, application=application).first()
|
||||
if not grant or grant.role not in PERMISSIONS:
|
||||
return None
|
||||
return {
|
||||
"issuer": "django-codexpi",
|
||||
"subject": str(request.user.pk),
|
||||
"username": request.user.get_username(),
|
||||
"application": application,
|
||||
"role": grant.role,
|
||||
"permissions": list(PERMISSIONS[grant.role]),
|
||||
}
|
||||
|
||||
|
||||
@require_GET
|
||||
@identity_request
|
||||
def application_identity(request, application):
|
||||
identity = principal(request, application)
|
||||
return reply(identity) if identity else reply({"detail": "Accès à cette application refusé"}, 403)
|
||||
|
||||
|
||||
@require_POST
|
||||
@identity_request
|
||||
def authorize(request, application):
|
||||
body = json_body(request)
|
||||
if set(body) != {"permission"} or body["permission"] not in ("read", "write", "admin"):
|
||||
return reply({"detail": "Permission invalide"}, 400)
|
||||
identity = principal(request, application)
|
||||
if not identity or body["permission"] not in identity["permissions"]:
|
||||
return reply({"detail": "Permission refusée"}, 403)
|
||||
return reply({**identity, "authorized_permission": body["permission"]})
|
||||
|
||||
|
||||
@require_http_methods(["GET", "PUT", "DELETE"])
|
||||
@identity_request
|
||||
def application_grant(request, application, user_id):
|
||||
if not (request.user.is_staff or request.user.is_superuser):
|
||||
return reply({"detail": "Administration des identités requise"}, 403)
|
||||
target = get_user_model().objects.filter(pk=user_id).first()
|
||||
if not target:
|
||||
return reply({"detail": "Compte introuvable"}, 404)
|
||||
grants = ApplicationGrant.objects.filter(user=target, application=application)
|
||||
if request.method == "GET":
|
||||
grant = grants.first()
|
||||
return reply({"subject": str(target.pk), "application": application, "role": grant.role if grant else None})
|
||||
if request.method == "DELETE":
|
||||
grants.delete()
|
||||
role = None
|
||||
else:
|
||||
body = json_body(request)
|
||||
if set(body) != {"role"} or body["role"] not in ROLES:
|
||||
return reply({"detail": "Rôle invalide"}, 400)
|
||||
role = body["role"]
|
||||
ApplicationGrant.objects.update_or_create(user=target, application=application, defaults={"role": role})
|
||||
AUDIT.log(
|
||||
"identity.grant_revoked" if role is None else "identity.grant_updated",
|
||||
client_ip(request),
|
||||
username=request.user.get_username(),
|
||||
project_id=application,
|
||||
outcome="allowed",
|
||||
reason=f"target_user={target.pk};role={role or 'none'}",
|
||||
)
|
||||
return reply({"subject": str(target.pk), "application": application, "role": role})
|
||||
Reference in New Issue
Block a user