Authorization-code flow with PKCE through an idp adapter (with a fake), ID token validation, identity and roles resolved from the directory with a short cache, refusal of unknown, disabled and locked accounts with one generic page, sessions that end when the account becomes unavailable, sign-out that ends the server-side session, role decorators and the trusted-proxy client address rule. Adds the OIDC_* and PUBLIC_BASE_URL configuration. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
40 lines
1.1 KiB
Python
40 lines
1.1 KiB
Python
"""Access control for views: signed in, Publisher, Admin (FR-1, FR-2, FR-25)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import wraps
|
|
from urllib.parse import urlencode
|
|
|
|
from django.shortcuts import redirect, render
|
|
from django.urls import reverse
|
|
|
|
|
|
def login_required(view):
|
|
@wraps(view)
|
|
def wrapper(request, *args, **kwargs):
|
|
if getattr(request, "portal_user", None) is None:
|
|
target = reverse("login")
|
|
if request.method in ("GET", "HEAD"):
|
|
target += "?" + urlencode({"next": request.get_full_path()})
|
|
return redirect(target)
|
|
return view(request, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
def _role_required(attribute: str):
|
|
def decorator(view):
|
|
@wraps(view)
|
|
def wrapper(request, *args, **kwargs):
|
|
if not getattr(request.portal_user, attribute):
|
|
return render(request, "accounts/forbidden.html", status=403)
|
|
return view(request, *args, **kwargs)
|
|
|
|
return login_required(wrapper)
|
|
|
|
return decorator
|
|
|
|
|
|
publisher_required = _role_required("is_publisher")
|
|
admin_required = _role_required("is_admin")
|