stages/email: check saved get params for token

This commit is contained in:
Jens Langhammer 2020-06-21 20:46:48 +02:00
parent de1be2df88
commit 491e507d49
3 changed files with 49 additions and 22 deletions

View File

@ -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)

View File

@ -15,7 +15,7 @@
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<button class="pf-c-button pf-m-primary pf-m-block" type="submit">{% trans "Send Email." %}</button>
<button class="pf-c-button pf-m-block" type="submit">{% trans "Send Email again." %}</button>
</div>
</form>
{% endblock %}

View File

@ -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(