"""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})