diff --git a/membership/members/middleware.py b/membership/members/middleware.py index 6e1bdda..51c7897 100755 --- a/membership/members/middleware.py +++ b/membership/members/middleware.py @@ -5,6 +5,9 @@ class SecurityHeaders: def __call__(self, request): response = self.get_response(request) response["Cache-Control"] = "no-store" - response["Referrer-Policy"] = "no-referrer" + # HTTPS form posts need their same-site origin/referrer for Django's + # CSRF checks. no-referrer can produce Origin: null in browsers. + # same-origin still withholds verification URLs from external sites. + response["Referrer-Policy"] = "same-origin" response["Content-Security-Policy"] = "default-src 'none'; style-src 'self'; img-src 'self'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'" return response diff --git a/membership/members/tests/test_membership.py b/membership/members/tests/test_membership.py index 279e218..ba298b3 100755 --- a/membership/members/tests/test_membership.py +++ b/membership/members/tests/test_membership.py @@ -109,6 +109,24 @@ class RegistrationTests(TestCase): self.assertEqual(self.client.get("/members/login/").status_code, 405) self.assertEqual(self.client.get("/members/checkout/supporter/").status_code, 405) + @patch("members.views.dex") + def test_same_origin_login_preserves_csrf_protection(self, dex): + from django.http import HttpResponseRedirect + + strict = Client(enforce_csrf_checks=True) + response = strict.get("/members/", secure=True) + self.assertEqual(response.headers["Referrer-Policy"], "same-origin") + token = strict.cookies[settings.CSRF_COOKIE_NAME].value + dex.return_value.authorize_redirect.return_value = HttpResponseRedirect(settings.DEX_ISSUER + "/auth") + response = strict.post("/members/login/", {"csrfmiddlewaretoken": token}, secure=True, HTTP_ORIGIN="https://testserver") + self.assertEqual(response.status_code, 302) + dex.return_value.authorize_redirect.assert_called_once() + for origin in ("null", "https://attacker.example"): + response = strict.post("/members/login/", {"csrfmiddlewaretoken": token}, secure=True, HTTP_ORIGIN=origin) + self.assertEqual(response.status_code, 403) + response = strict.post("/members/login/", {"csrfmiddlewaretoken": token}, secure=True, HTTP_REFERER="https://testserver/members/") + self.assertEqual(response.status_code, 302) + @override_settings(MEMBERSHIP_TIERS=TIERS) class IdentityTests(TestCase):