diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py index cfecd672d..c9835cab6 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -23,6 +23,7 @@ from authentik.stages.email.utils import TemplateEmailMessage LOGGER = get_logger() PLAN_CONTEXT_EMAIL_SENT = "email_sent" +PLAN_CONTEXT_EMAIL_OVERRIDE = "email" class EmailChallenge(Challenge): @@ -83,13 +84,16 @@ class EmailStageView(ChallengeStageView): """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] + 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 token = self.get_token() # Send mail to user message = TemplateEmailMessage( subject=_(current_stage.subject), template_name=current_stage.template, - to=[pending_user.email], + to=[email], template_context={ "url": self.get_full_url(**{QS_KEY_TOKEN: token.key}), "user": pending_user, diff --git a/authentik/stages/email/tests/test_stage.py b/authentik/stages/email/tests/test_stage.py index 79bea37aa..0434e144d 100644 --- a/authentik/stages/email/tests/test_stage.py +++ b/authentik/stages/email/tests/test_stage.py @@ -14,7 +14,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from authentik.flows.tests import FlowTestCase from authentik.flows.views.executor import SESSION_KEY_PLAN 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): @@ -75,6 +75,27 @@ class TestEmailStage(FlowTestCase): 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, ["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): """Test use_global_settings""" diff --git a/website/docs/flow/stages/email/index.md b/website/docs/flow/stages/email/index.md index 281b027ce..406d32132 100644 --- a/website/docs/flow/stages/email/index.md +++ b/website/docs/flow/stages/email/index.md @@ -6,6 +6,21 @@ This stage can be used for email verification. authentik's background worker wil ![](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 You can also use custom email templates, to use your own design or layout.