Files
ping-mee-self-service-platform/portal/config/middleware.py
T
Ebbe BaßandClaude Sonnet 5.5 f8b0584687 feat: add Django project scaffold, configuration and container
Environment-variable configuration that fails fast, database-only health
check, JSON request logs with a request id, security headers with a strict
CSP, and a multi-stage non-root Docker image with compose file. Ticket 1.1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
2026-10-15 13:44:07 +02:00

71 lines
2.2 KiB
Python

"""Request id, request logging and security headers (AD-16, AD-18)."""
from __future__ import annotations
import logging
import re
import time
import uuid
from .logging import request_id_var
logger = logging.getLogger("portal.request")
_VALID_REQUEST_ID = re.compile(r"^[A-Za-z0-9._-]{8,64}$")
CSP = (
"default-src 'self'; "
"script-src 'self'; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self'"
)
PERMISSIONS_POLICY = "camera=(), microphone=(), geolocation=(), payment=()"
class RequestIdMiddleware:
"""Assign a request id, set the response header and write one log line."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
incoming = request.META.get("HTTP_X_REQUEST_ID", "")
request_id = incoming if _VALID_REQUEST_ID.match(incoming) else uuid.uuid4().hex
request.request_id = request_id
token = request_id_var.set(request_id)
started = time.monotonic()
status = 500
try:
response = self.get_response(request)
status = response.status_code
response["X-Request-ID"] = request_id
return response
finally:
# Path only: query strings and bodies are never logged.
logger.info(
"request",
extra={
"request_id": request_id,
"method": request.method,
"path": request.path,
"status": status,
"duration_ms": round((time.monotonic() - started) * 1000, 1),
},
)
request_id_var.reset(token)
class SecurityHeadersMiddleware:
"""Add the Content-Security-Policy and related headers to every response."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response.headers.setdefault("Content-Security-Policy", CSP)
response.headers.setdefault("Permissions-Policy", PERMISSIONS_POLICY)
response.headers.setdefault("Cross-Origin-Opener-Policy", "same-origin")
return response