stages/authenticator_sms: make sms stage payload customisable (#3780)
* make sms stage payload customisable Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * update phrasing for webhook mapping Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
c4cc04918b
commit
217e145d23
|
@ -22,4 +22,8 @@ class Migration(migrations.Migration):
|
|||
default="local",
|
||||
),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="notificationwebhookmapping",
|
||||
options={"verbose_name": "Webhook Mapping", "verbose_name_plural": "Webhook Mappings"},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -27,6 +27,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
|
|||
"auth_password",
|
||||
"auth_type",
|
||||
"verify_only",
|
||||
"mapping",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
|
30
schema.yml
30
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
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
AuthenticatorSMSStage,
|
||||
FlowsApi,
|
||||
FlowsInstancesListDesignationEnum,
|
||||
PropertymappingsApi,
|
||||
ProviderEnum,
|
||||
StagesApi,
|
||||
} from "@goauthentik/api";
|
||||
|
@ -91,7 +92,8 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
|
|||
}
|
||||
|
||||
renderProviderGeneric(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal
|
||||
return html`
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authentication Type`}
|
||||
@change=${(ev: Event) => {
|
||||
const current = (ev.target as HTMLInputElement).value;
|
||||
|
@ -153,7 +155,33 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
|
|||
<p class="pf-c-form__helper-text">
|
||||
${t`This is the password to be used with basic auth`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>`;
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Mapping`} name="mapping">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.mapping === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new PropertymappingsApi(DEFAULT_CONFIG)
|
||||
.propertymappingsNotificationList({})
|
||||
.then((mappings) => {
|
||||
return mappings.results.map((mapping) => {
|
||||
return html`<option
|
||||
value=${ifDefined(mapping.pk)}
|
||||
?selected=${this.instance?.mapping === mapping.pk}
|
||||
>
|
||||
${mapping.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Modify the payload sent to the custom provider.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
`;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in New Issue