Skip to content

stop_impersonating is CSRF-exempt — find root cause and restore protection #176

Description

@alexeygrigorev

Summary

The stop_impersonating view was made @csrf_exempt in f644e5a to unblock staff returning to their own account after impersonating a user. The impersonation-stop POST was failing CSRF verification. Exempting the view works around the symptom but masks the real cause and should be treated as temporary.

Why this is not just cosmetic

The banner form already renders {% csrf_token %} (templates/base.html:67), so this is not a missing-token problem. A token is being sent and is still being rejected — something environmental is rejecting a valid request, and we don't currently know what. If we don't understand why it fails here, we can't be sure it isn't (or won't start) failing on other POST forms.

Risk assessment

Practical risk on this specific endpoint is low: stop_impersonating only calls restore_original_login and redirects. It is @login_required and @require_POST. The worst a CSRF attacker achieves is forcing a staff member who is currently impersonating to drop back to their own account — a de-escalation, not a privilege gain, with no data mutation.

The concern is not this endpoint today, it's that:

  • @csrf_exempt masks an unexplained CSRF failure.
  • It sets a precedent that becomes dangerous the moment someone adds real logic to the view.

Likely root causes (to investigate)

The Django CSRF rejection log line states the exact reason. Candidates, most to least likely:

  1. Origin/Referer rejection behind the proxy. CSRF_TRUSTED_ORIGINS is derived from ALLOWED_HOSTS / the EXTRA_ALLOWED_HOSTS env var (course_management/settings.py:42-46). If dev.courses.datatalks.club isn't in that env var, HTTPS POSTs get "Origin checking failed" — though this would affect all forms, not just this one.
  2. Stale/cached token. If the banner-bearing page is served from a cache, the rendered token won't match the user's current csrftoken cookie. This would be page-specific.
  3. CSRF cookie lost across the django-loginas session rotation, possibly interacting with CSRF_COOKIE_SECURE / SameSite behind the TLS-terminating proxy (SECURE_PROXY_SSL_HEADER is set at settings.py:318).

Proposed fix

  1. Pull the actual CSRF failure reason from dev logs (or reproduce locally) to identify which cause above applies.
  2. Apply the targeted fix:
    • Origin issue → add the host to EXTRA_ALLOWED_HOSTS.
    • Caching issue → @never_cache the banner-bearing view (keeps CSRF protection).
    • Cookie issue → fix the cookie flag / proxy config.
  3. Remove @csrf_exempt from stop_impersonating (accounts/views.py:62) once the real cause is fixed.

References

  • Commit: f644e5a "Exempt stop_impersonating from CSRF"
  • accounts/views.py:61-66
  • templates/base.html:66-71
  • course_management/settings.py:42-46, :318

Metadata

Metadata

Assignees

No one assigned

    Labels

    infraRequires AWS/CI/deploy/prod-environment work; not solvable in the app code alone

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions