1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

feat(accounts): Django's LoginRequiredMiddleware replaces the package

django-login-required-middleware is dropped; Django 5.1+ ships the same
middleware. The login page is already marked login_not_required by
Django; email_otp gets the decorator, which replaces
LOGIN_REQUIRED_IGNORE_VIEW_NAMES. The datasource still requires login.

Existing settings.py files still name the old middleware. A system
check (accounts.E002) stops migrate and the server until MIDDLEWARE
uses Django's middleware, instead of an ImportError at startup; the
README has the steps.

A new matrix test walks every URL anonymously: everything outside a
short allowlist (login pages, the OpenAPI docs) sends the visitor to
the login page, and API views deny with 401/403. It passed with the old
middleware first and passes unchanged now.
This commit is contained in:
catborise 2026-10-03 15:51:09 +03:00
parent 920f318601
commit e94d69a524
7 changed files with 80 additions and 5 deletions

View file

@ -78,7 +78,7 @@ MIDDLEWARE = [
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django_otp.middleware.OTPMiddleware",
"login_required.middleware.LoginRequiredMiddleware",
"django.contrib.auth.middleware.LoginRequiredMiddleware",
"accounts.middleware.ForcePasswordChangeMiddleware",
"django.contrib.auth.middleware.RemoteUserMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
@ -293,7 +293,6 @@ SHOW_PROFILE_EDIT_PASSWORD = True
# OTP Config
OTP_ENABLED = False
LOGIN_REQUIRED_IGNORE_VIEW_NAMES = ["accounts:login", "accounts:email_otp"]
# REST Framework & OpenAPI (drf-spectacular)
REST_FRAMEWORK = {

View file

@ -55,6 +55,18 @@ ALLOWED_FOR_ANY_USER = {
"schema-swagger-ui": "API docs (SERVE_PERMISSIONS is S-21, Wave 3)",
}
# URL name -> why an anonymous visitor may reach it. Everything else must
# redirect to the login page.
ALLOWED_FOR_ANONYMOUS = {
"accounts:login": "login page",
"accounts:email_otp": "part of the login flow (S-12 tracks its own issues)",
"rest_framework:login": "DRF browsable API login page",
"schema": "OpenAPI schema (SERVE_PERMISSIONS is S-21, Wave 3)",
"schema-json": "OpenAPI schema (SERVE_PERMISSIONS is S-21, Wave 3)",
"schema-redoc": "API docs (SERVE_PERMISSIONS is S-21, Wave 3)",
"schema-swagger-ui": "API docs (SERVE_PERMISSIONS is S-21, Wave 3)",
}
# Endpoints that expose a VM's console (or its VNC password) and therefore
# must follow the console rule: superuser or VM owner, not global view_instances.
CONSOLE_ENDPOINTS = ["instances:getvvfile", "vdi_url"]
@ -210,6 +222,25 @@ class AuthorizationMatrixTestCase(TestCase):
"Reachable by a user without any VM/compute relation:\n" + "\n".join(reachable),
)
def test_anonymous_visitor_is_sent_to_login_everywhere_else(self):
from django.conf import settings
reachable = []
for name, params in iter_patterns(get_resolver().url_patterns):
if name in ALLOWED_FOR_ANONYMOUS:
continue
url = self._url(name, params)
for method in (self.client.get, self.client.post):
self.client.logout()
response = method(url)
# DRF views opt out of login middleware and deny by their own
# permission classes.
api_denied = url.startswith("/api/") and response.status_code in (401, 403)
to_login = response.status_code == 302 and response["Location"].startswith(settings.LOGIN_URL)
if not (api_denied or to_login):
reachable.append(f"{method.__name__.upper()} {name} ({url}) -> {response.status_code}")
self.assertEqual(reachable, [], "Reachable without logging in:\n" + "\n".join(reachable))
def test_allowlist_only_names_existing_urls(self):
names = {name for name, _ in iter_patterns(get_resolver().url_patterns)}
self.assertEqual(sorted(set(ALLOWED_FOR_ANY_USER) - names), [])