From b75b06255993d107feee7ec0b06875fb465dea6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Fri, 16 Jul 2021 12:01:44 +0300 Subject: [PATCH] Remove unsigned token support in hc.front.views.unsubscribe_email --- hc/front/tests/test_unsubscribe_email.py | 32 ++++++++---------------- hc/front/views.py | 28 ++++++++++----------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/hc/front/tests/test_unsubscribe_email.py b/hc/front/tests/test_unsubscribe_email.py index dfc30a8d..5cb75a57 100644 --- a/hc/front/tests/test_unsubscribe_email.py +++ b/hc/front/tests/test_unsubscribe_email.py @@ -13,31 +13,24 @@ class UnsubscribeEmailTestCase(BaseTestCase): self.channel.value = "alice@example.org" self.channel.save() - def test_it_serves_confirmation_form(self): - token = self.channel.make_token() - url = "/integrations/%s/unsub/%s/" % (self.channel.code, token) + signer = TimestampSigner(salt="alerts") + signed_token = signer.sign(self.channel.make_token()) + self.url = f"/integrations/{self.channel.code}/unsub/{signed_token}/" - r = self.client.get(url) + def test_it_serves_confirmation_form(self): + r = self.client.get(self.url) self.assertContains(r, "Please press the button below") self.assertNotContains(r, "submit()") def test_post_unsubscribes(self): - token = self.channel.make_token() - url = "/integrations/%s/unsub/%s/" % (self.channel.code, token) - - r = self.client.post(url) + r = self.client.post(self.url) self.assertContains(r, "has been unsubscribed", status_code=200) q = Channel.objects.filter(code=self.channel.code) self.assertEqual(q.count(), 0) def test_fresh_signature_does_not_autosubmit(self): - signer = TimestampSigner(salt="alerts") - signed_token = signer.sign(self.channel.make_token()) - - url = "/integrations/%s/unsub/%s/" % (self.channel.code, signed_token) - - r = self.client.get(url) + r = self.client.get(self.url) self.assertContains( r, "Please press the button below to unsubscribe", status_code=200 ) @@ -49,7 +42,7 @@ class UnsubscribeEmailTestCase(BaseTestCase): signer = TimestampSigner(salt="alerts") signed_token = signer.sign(self.channel.make_token()) - url = "/integrations/%s/unsub/%s/" % (self.channel.code, signed_token) + url = f"/integrations/{self.channel.code}/unsub/{signed_token}/" r = self.client.get(url) self.assertContains( @@ -59,13 +52,13 @@ class UnsubscribeEmailTestCase(BaseTestCase): def test_it_checks_signature(self): signed_token = self.channel.make_token() + ":bad:signature" - url = "/integrations/%s/unsub/%s/" % (self.channel.code, signed_token) + url = f"/integrations/{self.channel.code}/unsub/{signed_token}/" r = self.client.get(url) self.assertContains(r, "link you just used is incorrect", status_code=200) def test_it_checks_token(self): - url = "/integrations/%s/unsub/faketoken/" % self.channel.code + url = f"/integrations/{self.channel.code}/unsub/faketoken/" r = self.client.get(url) self.assertContains(r, "link you just used is incorrect", status_code=200) @@ -74,8 +67,5 @@ class UnsubscribeEmailTestCase(BaseTestCase): self.channel.kind = "webhook" self.channel.save() - token = self.channel.make_token() - url = "/integrations/%s/unsub/%s/" % (self.channel.code, token) - - r = self.client.get(url) + r = self.client.get(self.url) self.assertEqual(r.status_code, 404) diff --git a/hc/front/views.py b/hc/front/views.py index bfc8cde8..6d55d786 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -880,27 +880,25 @@ def verify_email(request, code, token): @csrf_exempt def unsubscribe_email(request, code, signed_token): + ctx = {} + # Some email servers open links in emails to check for malicious content. # To work around this, on GET requests we serve a confirmation form. # If the signature is at least 5 minutes old, we also include JS code to # auto-submit the form. - ctx = {} - if ":" in signed_token: - signer = signing.TimestampSigner(salt="alerts") - # First, check the signature without looking at the timestamp: - try: - token = signer.unsign(signed_token) - except signing.BadSignature: - return render(request, "bad_link.html") + signer = signing.TimestampSigner(salt="alerts") - # Check if timestamp is older than 5 minutes: - try: - signer.unsign(signed_token, max_age=300) - except signing.SignatureExpired: - ctx["autosubmit"] = True + # First, check the signature without looking at the timestamp: + try: + token = signer.unsign(signed_token) + except signing.BadSignature: + return render(request, "bad_link.html") - else: - token = signed_token + # Then, check if timestamp is older than 5 minutes: + try: + signer.unsign(signed_token, max_age=300) + except signing.SignatureExpired: + ctx["autosubmit"] = True channel = get_object_or_404(Channel, code=code, kind="email") if channel.make_token() != token: