stages/email: allow overriding of destination email in plan context

closes #2445

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2022-03-22 21:19:21 +01:00
parent 0c0b9ca84a
commit 9ad4c736f1
3 changed files with 42 additions and 2 deletions

View File

@ -23,6 +23,7 @@ from authentik.stages.email.utils import TemplateEmailMessage
LOGGER = get_logger() LOGGER = get_logger()
PLAN_CONTEXT_EMAIL_SENT = "email_sent" PLAN_CONTEXT_EMAIL_SENT = "email_sent"
PLAN_CONTEXT_EMAIL_OVERRIDE = "email"
class EmailChallenge(Challenge): class EmailChallenge(Challenge):
@ -83,13 +84,16 @@ class EmailStageView(ChallengeStageView):
"""Helper function that sends the actual email. Implies that you've """Helper function that sends the actual email. Implies that you've
already checked that there is a pending user.""" already checked that there is a pending user."""
pending_user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] pending_user = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
email = self.executor.plan.context.get(PLAN_CONTEXT_EMAIL_OVERRIDE, None)
if not email:
email = pending_user.email
current_stage: EmailStage = self.executor.current_stage current_stage: EmailStage = self.executor.current_stage
token = self.get_token() token = self.get_token()
# Send mail to user # Send mail to user
message = TemplateEmailMessage( message = TemplateEmailMessage(
subject=_(current_stage.subject), subject=_(current_stage.subject),
template_name=current_stage.template, template_name=current_stage.template,
to=[pending_user.email], to=[email],
template_context={ template_context={
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}), "url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"user": pending_user, "user": pending_user,

View File

@ -14,7 +14,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.email.models import EmailStage from authentik.stages.email.models import EmailStage
from authentik.stages.email.stage import QS_KEY_TOKEN from authentik.stages.email.stage import PLAN_CONTEXT_EMAIL_OVERRIDE, QS_KEY_TOKEN
class TestEmailStage(FlowTestCase): class TestEmailStage(FlowTestCase):
@ -75,6 +75,27 @@ class TestEmailStage(FlowTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik") self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertEqual(mail.outbox[0].to, ["test@beryju.org"])
def test_pending_user_override(self):
"""Test with pending user (override to)"""
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[PLAN_CONTEXT_EMAIL_OVERRIDE] = "foo@bar.baz"
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
with patch(
"authentik.stages.email.models.EmailStage.backend_class",
PropertyMock(return_value=EmailBackend),
):
response = self.client.post(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "authentik")
self.assertEqual(mail.outbox[0].to, ["foo@bar.baz"])
def test_use_global_settings(self): def test_use_global_settings(self):
"""Test use_global_settings""" """Test use_global_settings"""

View File

@ -6,6 +6,21 @@ This stage can be used for email verification. authentik's background worker wil
![](email_recovery.png) ![](email_recovery.png)
## Behaviour
By default, the email is sent to the currently pending user. To override this, you can set `email` in the plan's context to another email address, which will override the user's email address (the user won't be changed).
For example, create this expression policy and bind it to the email stage:
```python
request.context["email"] = "foo@bar.baz"
# Or get it from a prompt
# request.context["email"] = request.context["prompt_data"]["email"]
# Or another user attribute
# request.context["email"] = request.context["pending_user"].attributes.get("otherEmail")
return True
```
## Custom Templates ## Custom Templates
You can also use custom email templates, to use your own design or layout. You can also use custom email templates, to use your own design or layout.