diff --git a/authentik/events/migrations/0002_alter_notificationtransport_mode.py b/authentik/events/migrations/0002_alter_notificationtransport_mode.py index 5807b42e4..5faa5c457 100644 --- a/authentik/events/migrations/0002_alter_notificationtransport_mode.py +++ b/authentik/events/migrations/0002_alter_notificationtransport_mode.py @@ -22,4 +22,8 @@ class Migration(migrations.Migration): default="local", ), ), + migrations.AlterModelOptions( + name="notificationwebhookmapping", + options={"verbose_name": "Webhook Mapping", "verbose_name_plural": "Webhook Mappings"}, + ), ] diff --git a/authentik/events/models.py b/authentik/events/models.py index 629e9ba3a..f497afaed 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -560,7 +560,7 @@ class NotificationRule(SerializerModel, PolicyBindingModel): class NotificationWebhookMapping(PropertyMapping): - """Modify the schema and layout of the webhook being sent""" + """Modify the payload of outgoing webhook requests""" @property def component(self) -> str: @@ -573,9 +573,9 @@ class NotificationWebhookMapping(PropertyMapping): return NotificationWebhookMappingSerializer def __str__(self): - return f"Notification Webhook Mapping {self.name}" + return f"Webhook Mapping {self.name}" class Meta: - verbose_name = _("Notification Webhook Mapping") - verbose_name_plural = _("Notification Webhook Mappings") + verbose_name = _("Webhook Mapping") + verbose_name_plural = _("Webhook Mappings") diff --git a/authentik/providers/saml/models.py b/authentik/providers/saml/models.py index 660bc9afe..172ff956c 100644 --- a/authentik/providers/saml/models.py +++ b/authentik/providers/saml/models.py @@ -182,7 +182,7 @@ class SAMLProvider(Provider): class SAMLPropertyMapping(PropertyMapping): - """Map User/Group attribute to SAML Attribute, which can be used by the Service Provider.""" + """Map User/Group attribute to SAML Attribute, which can be used by the Service Provider""" saml_name = models.TextField(verbose_name="SAML Name") friendly_name = models.TextField(default=None, blank=True, null=True) diff --git a/authentik/stages/authenticator_sms/api.py b/authentik/stages/authenticator_sms/api.py index 6e632480d..cf5d39c89 100644 --- a/authentik/stages/authenticator_sms/api.py +++ b/authentik/stages/authenticator_sms/api.py @@ -27,6 +27,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer): "auth_password", "auth_type", "verify_only", + "mapping", ] diff --git a/authentik/stages/authenticator_sms/migrations/0005_authenticatorsmsstage_mapping.py b/authentik/stages/authenticator_sms/migrations/0005_authenticatorsmsstage_mapping.py new file mode 100644 index 000000000..bf9f8d822 --- /dev/null +++ b/authentik/stages/authenticator_sms/migrations/0005_authenticatorsmsstage_mapping.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.2 on 2022-10-13 20:19 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_events", "0002_alter_notificationtransport_mode"), + ("authentik_stages_authenticator_sms", "0004_authenticatorsmsstage_verify_only_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="authenticatorsmsstage", + name="mapping", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="authentik_events.notificationwebhookmapping", + help_text="Optionally modify the payload being sent to custom providers.", + ), + ), + ] diff --git a/authentik/stages/authenticator_sms/models.py b/authentik/stages/authenticator_sms/models.py index 4ad2e2a5f..70017b96f 100644 --- a/authentik/stages/authenticator_sms/models.py +++ b/authentik/stages/authenticator_sms/models.py @@ -15,7 +15,8 @@ from twilio.base.exceptions import TwilioRestException from twilio.rest import Client from authentik.core.types import UserSettingSerializer -from authentik.events.models import Event, EventAction +from authentik.events.models import Event, EventAction, NotificationWebhookMapping +from authentik.events.utils import sanitize_item from authentik.flows.models import ConfigurableStage, Stage from authentik.lib.models import SerializerModel from authentik.lib.utils.errors import exception_to_string @@ -59,6 +60,14 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage): ), ) + mapping = models.ForeignKey( + NotificationWebhookMapping, + null=True, + default=None, + on_delete=models.SET_NULL, + help_text=_("Optionally modify the payload being sent to custom providers."), + ) + def send(self, token: str, device: "SMSDevice"): """Send message via selected provider""" if self.provider == SMSProviders.TWILIO: @@ -82,24 +91,33 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage): def send_generic(self, token: str, device: "SMSDevice"): """Send SMS via outside API""" - - data = { + payload = { "From": self.from_number, "To": device.phone_number, "Body": token, } + if self.mapping: + payload = sanitize_item( + self.mapping.evaluate( + user=device.user, + request=None, + device=device, + token=token, + stage=self, + ) + ) + if self.auth_type == SMSAuthTypes.BEARER: response = get_http_session().post( f"{self.account_sid}", - json=data, + json=payload, headers={"Authorization": f"Bearer {self.auth}"}, ) - elif self.auth_type == SMSAuthTypes.BASIC: response = get_http_session().post( f"{self.account_sid}", - json=data, + json=payload, auth=(self.auth, self.auth_password), ) else: diff --git a/schema.yml b/schema.yml index 89127066f..1fe621123 100644 --- a/schema.yml +++ b/schema.yml @@ -13101,7 +13101,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this Notification Webhook Mapping. + description: A UUID string identifying this Webhook Mapping. required: true tags: - propertymappings @@ -13135,7 +13135,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this Notification Webhook Mapping. + description: A UUID string identifying this Webhook Mapping. required: true tags: - propertymappings @@ -13175,7 +13175,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this Notification Webhook Mapping. + description: A UUID string identifying this Webhook Mapping. required: true tags: - propertymappings @@ -13214,7 +13214,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this Notification Webhook Mapping. + description: A UUID string identifying this Webhook Mapping. required: true tags: - propertymappings @@ -13245,7 +13245,7 @@ paths: schema: type: string format: uuid - description: A UUID string identifying this Notification Webhook Mapping. + description: A UUID string identifying this Webhook Mapping. required: true tags: - propertymappings @@ -18794,6 +18794,11 @@ paths: name: from_number schema: type: string + - in: query + name: mapping + schema: + type: string + format: uuid - in: query name: name schema: @@ -25185,6 +25190,11 @@ components: description: When enabled, the Phone number is only used during enrollment to verify the users authenticity. Only a hash of the phone number is saved to ensure it is not re-used in the future. + mapping: + type: string + format: uuid + nullable: true + description: Optionally modify the payload being sent to custom providers. required: - account_sid - auth @@ -25233,6 +25243,11 @@ components: description: When enabled, the Phone number is only used during enrollment to verify the users authenticity. Only a hash of the phone number is saved to ensure it is not re-used in the future. + mapping: + type: string + format: uuid + nullable: true + description: Optionally modify the payload being sent to custom providers. required: - account_sid - auth @@ -32702,6 +32717,11 @@ components: description: When enabled, the Phone number is only used during enrollment to verify the users authenticity. Only a hash of the phone number is saved to ensure it is not re-used in the future. + mapping: + type: string + format: uuid + nullable: true + description: Optionally modify the payload being sent to custom providers. PatchedAuthenticatorStaticStageRequest: type: object description: AuthenticatorStaticStage Serializer diff --git a/web/src/admin/stages/authenticator_sms/AuthenticatorSMSStageForm.ts b/web/src/admin/stages/authenticator_sms/AuthenticatorSMSStageForm.ts index 0ad8a3918..8ae544604 100644 --- a/web/src/admin/stages/authenticator_sms/AuthenticatorSMSStageForm.ts +++ b/web/src/admin/stages/authenticator_sms/AuthenticatorSMSStageForm.ts @@ -16,6 +16,7 @@ import { AuthenticatorSMSStage, FlowsApi, FlowsInstancesListDesignationEnum, + PropertymappingsApi, ProviderEnum, StagesApi, } from "@goauthentik/api"; @@ -91,7 +92,8 @@ export class AuthenticatorSMSStageForm extends ModelForm { const current = (ev.target as HTMLInputElement).value; @@ -153,7 +155,33 @@ export class AuthenticatorSMSStageForm extends ModelForm ${t`This is the password to be used with basic auth`}

- `; + + + +

+ ${t`Modify the payload sent to the custom provider.`} +

+
+ `; } renderForm(): TemplateResult { diff --git a/website/docs/flow/stages/authenticator_sms/index.md b/website/docs/flow/stages/authenticator_sms/index.md index faa7d34f4..656d80520 100644 --- a/website/docs/flow/stages/authenticator_sms/index.md +++ b/website/docs/flow/stages/authenticator_sms/index.md @@ -36,6 +36,16 @@ For the generic provider, a POST request will be sent to the URL you have specif Authentication can either be done as HTTP Basic, or via a Bearer Token. Any response with status 400 or above is counted as failed, and will prevent the user from proceeding. +Starting with authentik 2022.10, a custom webhook mapping can be specified to freely customise the payload of the request. For example: + +```python +return { + "from": stage.from_number, + "to": device.phone_number, + "body": f"foo bar baz {token}". +} +``` + ## Verify only :::info