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:
parent
920f318601
commit
e94d69a524
7 changed files with 80 additions and 5 deletions
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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), [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue