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:
- Is the view guarded?
- CBV: does its MRO include
LoginRequiredMixin,PermissionRequiredMixin,UserPassesTestMixin,AccessMixin,StaffRequiredMixin,SuperuserRequiredMixin? - CBV + DRF: does
permission_classesinclude a guarding permission (IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly,DjangoModelPermissions,DjangoObjectPermissions)?AllowAnyalone does not count as a guard. - 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 mentionrequest.user.is_authenticated? - 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. - 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. oldfunctools.wraps-less decorators) may be misclassified as unguarded. Add the URL name to the whitelist or rewrite the decorator withfunctools.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. AllowAnyis treated as "not guarded" on purpose. If a DRF endpoint is legitimately public, whitelist its URL name.