From 491e507d49e0d982c87a0239bc4eb9c04450ec96 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 21 Jun 2020 20:46:48 +0200 Subject: [PATCH] stages/email: check saved get params for token --- passbook/stages/email/stage.py | 55 +++++++++++++------ .../stages/email/waiting_message.html | 2 +- passbook/stages/email/tests.py | 14 ++++- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/passbook/stages/email/stage.py b/passbook/stages/email/stage.py index a93ab563e..58f2ab03d 100644 --- a/passbook/stages/email/stage.py +++ b/passbook/stages/email/stage.py @@ -13,12 +13,15 @@ from structlog import get_logger from passbook.core.models import Token from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER from passbook.flows.stage import StageView +from passbook.flows.views import SESSION_KEY_GET from passbook.stages.email.forms import EmailStageSendForm +from passbook.stages.email.models import EmailStage from passbook.stages.email.tasks import send_mails from passbook.stages.email.utils import TemplateEmailMessage LOGGER = get_logger() QS_KEY_TOKEN = "token" +PLAN_CONTEXT_EMAIL_SENT = "email_sent" class EmailStageView(FormView, StageView): @@ -30,34 +33,25 @@ class EmailStageView(FormView, StageView): def get_full_url(self, **kwargs) -> str: """Get full URL to be used in template""" base_url = reverse( - "passbook_flows:flow-executor", + "passbook_flows:flow-executor-shell", kwargs={"flow_slug": self.executor.flow.slug}, ) relative_url = f"{base_url}?{urlencode(kwargs)}" return self.request.build_absolute_uri(relative_url) - def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: - if QS_KEY_TOKEN in request.GET: - token = get_object_or_404(Token, pk=request.GET[QS_KEY_TOKEN]) - self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user - token.delete() - messages.success(request, _("Successfully verified Email.")) - return self.executor.stage_ok() - return super().get(request, *args, **kwargs) - - def form_invalid(self, form: EmailStageSendForm) -> HttpResponse: - if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: - messages.error(self.request, _("No pending user.")) - return super().form_invalid(form) + def send_email(self): + """Helper function that sends the actual email. Implies that you've + already checked that there is a pending user.""" pending_user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] + current_stage: EmailStage = self.executor.current_stage valid_delta = timedelta( - minutes=self.executor.current_stage.token_expiry + 1 + minutes=current_stage.token_expiry + 1 ) # + 1 because django timesince always rounds down token = Token.objects.create(user=pending_user, expires=now() + valid_delta) # Send mail to user message = TemplateEmailMessage( - subject=_("passbook - Password Recovery"), - template_name=self.executor.current_stage.template, + subject=_(current_stage.subject), + template_name=current_stage.template, to=[pending_user.email], template_context={ "url": self.get_full_url(**{QS_KEY_TOKEN: token.pk.hex}), @@ -65,7 +59,32 @@ class EmailStageView(FormView, StageView): "expires": token.expires, }, ) - send_mails(self.executor.current_stage, message) + send_mails(current_stage, message) + + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + # Check if the user came back from the email link to verify + if QS_KEY_TOKEN in request.session.get(SESSION_KEY_GET, {}): + token = get_object_or_404( + Token, pk=request.session[SESSION_KEY_GET][QS_KEY_TOKEN] + ) + self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user + token.delete() + messages.success(request, _("Successfully verified Email.")) + return self.executor.stage_ok() + if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: + messages.error(self.request, _("No pending user.")) + return self.executor.stage_invalid() + # Check if we've already sent the initial e-mail + if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context: + self.send_email() + self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True + return super().get(request, *args, **kwargs) + + def form_invalid(self, form: EmailStageSendForm) -> HttpResponse: + if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: + messages.error(self.request, _("No pending user.")) + return super().form_invalid(form) + self.send_email() # We can't call stage_ok yet, as we're still waiting # for the user to click the link in the email return super().form_invalid(form) diff --git a/passbook/stages/email/templates/stages/email/waiting_message.html b/passbook/stages/email/templates/stages/email/waiting_message.html index 98f961333..5f1762624 100644 --- a/passbook/stages/email/templates/stages/email/waiting_message.html +++ b/passbook/stages/email/templates/stages/email/waiting_message.html @@ -15,7 +15,7 @@ {% block beneath_form %} {% endblock %}
- +
{% endblock %} diff --git a/passbook/stages/email/tests.py b/passbook/stages/email/tests.py index 43d7e2e7c..657cd1258 100644 --- a/passbook/stages/email/tests.py +++ b/passbook/stages/email/tests.py @@ -83,7 +83,7 @@ class TestEmailStage(TestCase): response = self.client.post(url) self.assertEqual(response.status_code, 200) self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, "passbook - Password Recovery") + self.assertEqual(mail.outbox[0].subject, "passbook") def test_token(self): """Test with token""" @@ -97,12 +97,20 @@ class TestEmailStage(TestCase): session.save() with patch("passbook.flows.views.FlowExecutorView.cancel", MagicMock()): + # Call the executor shell to preseed the session url = reverse( - "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug} + "passbook_flows:flow-executor-shell", + kwargs={"flow_slug": self.flow.slug}, ) token = Token.objects.get(user=self.user) url += f"?{QS_KEY_TOKEN}={token.pk.hex}" - response = self.client.get(url) + self.client.get(url) + # Call the actual executor to get the JSON Response + response = self.client.get( + reverse( + "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug} + ) + ) self.assertEqual(response.status_code, 200) self.assertJSONEqual(