import re from django.conf import settings from django.shortcuts import redirect # The administration area is decided by role, not by the staff flag alone. ADMIN_PATH = re.compile(r"^/admin/") class RBACMiddleware: ROLE_ROUTES = { "admin": [r"^/admin/", r"^/ops/", r"^/portal/"], "ops": [r"^/ops/"], "buyer": [r"^/portal/"], } def __init__(self, get_response): self.get_response = get_response def __call__(self, request): user = request.user if user.is_authenticated and not user.is_superuser: path = request.path_info user_role = getattr(request.user, "role", None) # /admin/ is for the admin role. is_staff alone is not enough: an ops # or buyer account carrying the flag must not reach the # administration area, whose index embeds the model registry, and # where a later group or permission grant would be inherited. A # superuser (above) is the deliberate exception. if ADMIN_PATH.match(path) and user_role != "admin": return redirect(settings.LOGIN_REDIRECT_URL) if not user.is_staff: allowed = self.ROLE_ROUTES.get(user_role, []) if any(re.match(pattern, path) for pattern in allowed): return self.get_response(request) # The path belongs to a portal this role may not enter. Send them # to the role-aware landing view rather than straight into a # portal they cannot use. It used to redirect if *any* role's # pattern matched the path without belonging to this role, which # bounced a buyer off /portal/ itself (the admin patterns include # /portal/), and looped with the landing redirect. if any( re.match(pattern, path) for patterns in self.ROLE_ROUTES.values() for pattern in patterns ): return redirect(settings.LOGIN_REDIRECT_URL) return self.get_response(request)