Files
ebbe.bassandClaude Sonnet 5.5 3c69c2a156 feat: add OIDC sign-in, roles, sessions and client address rule
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
2026-10-17 19:19:51 +02:00

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