stages/email: check saved get params for token
This commit is contained in:
parent
de1be2df88
commit
491e507d49
|
@ -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)
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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(
|
||||
|
|
Reference in New Issue