Skip to content

views — authorization audit

ID: views
Since: 0.6.0
Order: 60 (after urls, before post_smoke)
Needs DB: no
Severity: WARNING

What it catches

First-party views that appear to be public by accident. For every view bound to a URL pattern the check asks:

  1. Is the view guarded?
  2. CBV: does its MRO include LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin, AccessMixin, StaffRequiredMixin, SuperuserRequiredMixin?
  3. CBV + DRF: does permission_classes include a guarding permission (IsAuthenticated, IsAdminUser, IsAuthenticatedOrReadOnly, DjangoModelPermissions, DjangoObjectPermissions)? AllowAny alone does not count as a guard.
  4. FBV: is the callable wrapped by login_required / permission_required / staff_member_required / user_passes_test (detected by scanning the decorator's closure), or does the source mention request.user.is_authenticated?
  5. Is the URL name on the public whitelist? Defaults include login, logout, signup, register, password_reset*, health, healthcheck, healthz, readyz, livez, ping, home, index, schema, swagger, redoc, api-root, docs, favicon. Extend via [tool.django-doctor.views].public.
  6. Is the view part of a third-party app? (Skipped — we only audit first-party code.)

Views failing (1) and not excused by (2) surface as WARNING: opinionated but not CI-blocking by default. Override via fail_on if you want a hard stop.

Example finding

views — 1 finding(s)
┌─────────┬──────────────────┬────────────────────────────────────┬──────────────────────────────────────────────────────┐
│ Sev     │ Rule             │ Location                           │ Message                                              │
├─────────┼──────────────────┼────────────────────────────────────┼──────────────────────────────────────────────────────┤
│ WARNING │ views.unguarded  │ apps.core.views.DashboardView      │ dashboard appears to be public — no login/permission │
│         │                  │                                    │ guard was detected on the view.                      │
│         │                  │                                    │ → Add LoginRequiredMixin (or PermissionRequiredMixin)│
│         │                  │                                    │   for CBVs, @login_required / @permission_required   │
│         │                  │                                    │   for FBVs, or whitelist the URL name under          │
│         │                  │                                    │   [tool.django-doctor.views].public.                 │
└─────────┴──────────────────┴────────────────────────────────────┴──────────────────────────────────────────────────────┘

Configuration

[tool.django-doctor.views]
# URL names whose views are legitimately public. Tails of namespaced
# names also match, so "mandat:public-list" is silenced by "public-list".
public = ["landing", "pricing", "contact"]

# Glob patterns of view modules to skip entirely — useful for marketing
# pages that live under `apps.www` or similar.
skip_modules = ["apps.www.*"]

Known caveats

  • FBV decorators that shadow __wrapped__ (e.g. old functools.wraps-less decorators) may be misclassified as unguarded. Add the URL name to the whitelist or rewrite the decorator with functools.wraps.
  • CBVs that implement guarding inside dispatch() without using a mixin are flagged. The source heuristic (request.user.is_authenticated) will silence it when the pattern is obvious.
  • AllowAny is treated as "not guarded" on purpose. If a DRF endpoint is legitimately public, whitelist its URL name.