Browse Source

Unsubscribe links serve a form, and require HTTP POST to actually unsubscribe

pull/313/head
Pēteris Caune 5 years ago
parent
commit
8d81d27af3
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
7 changed files with 14 additions and 20 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +2
    -9
      hc/accounts/tests/test_unsubscribe_reports.py
  3. +1
    -1
      hc/accounts/views.py
  4. +5
    -0
      hc/api/tests/test_bounce.py
  5. +2
    -0
      hc/api/views.py
  6. +2
    -9
      hc/front/tests/test_unsubscribe_email.py
  7. +1
    -1
      hc/front/views.py

+ 1
- 0
CHANGELOG.md View File

@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
### Bug Fixes ### Bug Fixes
- Don't set CSRF cookie on first visit. Signup is exempt from CSRF protection - Don't set CSRF cookie on first visit. Signup is exempt from CSRF protection
- Fix List-Unsubscribe email header value: add angle brackets - Fix List-Unsubscribe email header value: add angle brackets
- Unsubscribe links serve a form, and require HTTP POST to actually unsubscribe
## v1.11.0 - 2019-11-22 ## v1.11.0 - 2019-11-22


+ 2
- 9
hc/accounts/tests/test_unsubscribe_reports.py View File

@ -15,7 +15,7 @@ class UnsubscribeReportsTestCase(BaseTestCase):
sig = signing.TimestampSigner(salt="reports").sign("alice") sig = signing.TimestampSigner(salt="reports").sign("alice")
url = "/accounts/unsubscribe_reports/%s/" % sig url = "/accounts/unsubscribe_reports/%s/" % sig
r = self.client.get(url)
r = self.client.post(url)
self.assertContains(r, "Unsubscribed") self.assertContains(r, "Unsubscribed")
self.profile.refresh_from_db() self.profile.refresh_from_db()
@ -30,16 +30,9 @@ class UnsubscribeReportsTestCase(BaseTestCase):
r = self.client.get(url) r = self.client.get(url)
self.assertContains(r, "Incorrect Link") self.assertContains(r, "Incorrect Link")
def test_post_works(self):
sig = signing.TimestampSigner(salt="reports").sign("alice")
url = "/accounts/unsubscribe_reports/%s/" % sig
r = self.client.post(url)
self.assertContains(r, "Unsubscribed")
def test_it_serves_confirmation_form(self): def test_it_serves_confirmation_form(self):
sig = signing.TimestampSigner(salt="reports").sign("alice") sig = signing.TimestampSigner(salt="reports").sign("alice")
url = "/accounts/unsubscribe_reports/%s/?ask=1" % sig
url = "/accounts/unsubscribe_reports/%s/" % sig
r = self.client.get(url) r = self.client.get(url)
self.assertContains(r, "Please press the button below") self.assertContains(r, "Please press the button below")

+ 1
- 1
hc/accounts/views.py View File

@ -442,7 +442,7 @@ def unsubscribe_reports(request, username):
# Some email servers open links in emails to check for malicious content. # Some email servers open links in emails to check for malicious content.
# To work around this, we serve a form that auto-submits with JS. # To work around this, we serve a form that auto-submits with JS.
if "ask" in request.GET and request.method != "POST":
if request.method != "POST":
return render(request, "accounts/unsubscribe_submit.html") return render(request, "accounts/unsubscribe_submit.html")
user = User.objects.get(username=username) user = User.objects.get(username=username)


+ 5
- 0
hc/api/tests/test_bounce.py View File

@ -49,3 +49,8 @@ class BounceTestCase(BaseTestCase):
url = "/api/v1/notifications/%s/bounce" % fake_code url = "/api/v1/notifications/%s/bounce" % fake_code
r = self.client.post(url, "", content_type="text/plain") r = self.client.post(url, "", content_type="text/plain")
self.assertEqual(r.status_code, 404) self.assertEqual(r.status_code, 404)
def test_it_requires_post(self):
url = "/api/v1/notifications/%s/bounce" % self.n.code
r = self.client.get(url)
self.assertEqual(r.status_code, 405)

+ 2
- 0
hc/api/views.py View File

@ -14,6 +14,7 @@ from django.shortcuts import get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from hc.api import schemas from hc.api import schemas
from hc.api.decorators import authorize, authorize_read, cors, validate_json from hc.api.decorators import authorize, authorize_read, cors, validate_json
@ -256,6 +257,7 @@ def badge(request, badge_key, signature, tag, fmt="svg"):
@csrf_exempt @csrf_exempt
@require_POST
def bounce(request, code): def bounce(request, code):
notification = get_object_or_404(Notification, code=code) notification = get_object_or_404(Notification, code=code)


+ 2
- 9
hc/front/tests/test_unsubscribe_email.py View File

@ -13,7 +13,7 @@ class UnsubscribeEmailTestCase(BaseTestCase):
token = self.channel.make_token() token = self.channel.make_token()
url = "/integrations/%s/unsub/%s/" % (self.channel.code, token) url = "/integrations/%s/unsub/%s/" % (self.channel.code, token)
r = self.client.get(url)
r = self.client.post(url)
self.assertContains(r, "has been unsubscribed", status_code=200) self.assertContains(r, "has been unsubscribed", status_code=200)
q = Channel.objects.filter(code=self.channel.code) q = Channel.objects.filter(code=self.channel.code)
@ -35,16 +35,9 @@ class UnsubscribeEmailTestCase(BaseTestCase):
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
def test_post_works(self):
token = self.channel.make_token()
url = "/integrations/%s/unsub/%s/" % (self.channel.code, token)
r = self.client.post(url)
self.assertContains(r, "has been unsubscribed", status_code=200)
def test_it_serves_confirmation_form(self): def test_it_serves_confirmation_form(self):
token = self.channel.make_token() token = self.channel.make_token()
url = "/integrations/%s/unsub/%s/?ask=1" % (self.channel.code, token)
url = "/integrations/%s/unsub/%s/" % (self.channel.code, token)
r = self.client.get(url) r = self.client.get(url)
self.assertContains(r, "Please press the button below") self.assertContains(r, "Please press the button below")

+ 1
- 1
hc/front/views.py View File

@ -712,7 +712,7 @@ def unsubscribe_email(request, code, token):
# Some email servers open links in emails to check for malicious content. # Some email servers open links in emails to check for malicious content.
# To work around this, we serve a form that auto-submits with JS. # To work around this, we serve a form that auto-submits with JS.
if "ask" in request.GET and request.method != "POST":
if request.method != "POST":
return render(request, "accounts/unsubscribe_submit.html") return render(request, "accounts/unsubscribe_submit.html")
channel.delete() channel.delete()


Loading…
Cancel
Save