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:
Jens L 2022-10-14 12:53:01 +03:00 committed by GitHub
parent c4cc04918b
commit 217e145d23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 18 deletions

View File

@ -22,4 +22,8 @@ class Migration(migrations.Migration):
default="local",
),
),
migrations.AlterModelOptions(
name="notificationwebhookmapping",
options={"verbose_name": "Webhook Mapping", "verbose_name_plural": "Webhook Mappings"},
),
]

View File

@ -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")

View File

@ -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)

View File

@ -27,6 +27,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
"auth_password",
"auth_type",
"verify_only",
"mapping",
]

View File

@ -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.",
),
),
]

View File

@ -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:

View File

@ -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

View File

@ -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 {

View File

@ -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