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", 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): class NotificationWebhookMapping(PropertyMapping):
"""Modify the schema and layout of the webhook being sent""" """Modify the payload of outgoing webhook requests"""
@property @property
def component(self) -> str: def component(self) -> str:
@ -573,9 +573,9 @@ class NotificationWebhookMapping(PropertyMapping):
return NotificationWebhookMappingSerializer return NotificationWebhookMappingSerializer
def __str__(self): def __str__(self):
return f"Notification Webhook Mapping {self.name}" return f"Webhook Mapping {self.name}"
class Meta: class Meta:
verbose_name = _("Notification Webhook Mapping") verbose_name = _("Webhook Mapping")
verbose_name_plural = _("Notification Webhook Mappings") verbose_name_plural = _("Webhook Mappings")

View File

@ -182,7 +182,7 @@ class SAMLProvider(Provider):
class SAMLPropertyMapping(PropertyMapping): 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") saml_name = models.TextField(verbose_name="SAML Name")
friendly_name = models.TextField(default=None, blank=True, null=True) friendly_name = models.TextField(default=None, blank=True, null=True)

View File

@ -27,6 +27,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
"auth_password", "auth_password",
"auth_type", "auth_type",
"verify_only", "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 twilio.rest import Client
from authentik.core.types import UserSettingSerializer 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.flows.models import ConfigurableStage, Stage
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_string 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"): def send(self, token: str, device: "SMSDevice"):
"""Send message via selected provider""" """Send message via selected provider"""
if self.provider == SMSProviders.TWILIO: if self.provider == SMSProviders.TWILIO:
@ -82,24 +91,33 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
def send_generic(self, token: str, device: "SMSDevice"): def send_generic(self, token: str, device: "SMSDevice"):
"""Send SMS via outside API""" """Send SMS via outside API"""
payload = {
data = {
"From": self.from_number, "From": self.from_number,
"To": device.phone_number, "To": device.phone_number,
"Body": token, "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: if self.auth_type == SMSAuthTypes.BEARER:
response = get_http_session().post( response = get_http_session().post(
f"{self.account_sid}", f"{self.account_sid}",
json=data, json=payload,
headers={"Authorization": f"Bearer {self.auth}"}, headers={"Authorization": f"Bearer {self.auth}"},
) )
elif self.auth_type == SMSAuthTypes.BASIC: elif self.auth_type == SMSAuthTypes.BASIC:
response = get_http_session().post( response = get_http_session().post(
f"{self.account_sid}", f"{self.account_sid}",
json=data, json=payload,
auth=(self.auth, self.auth_password), auth=(self.auth, self.auth_password),
) )
else: else:

View File

@ -13101,7 +13101,7 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
description: A UUID string identifying this Notification Webhook Mapping. description: A UUID string identifying this Webhook Mapping.
required: true required: true
tags: tags:
- propertymappings - propertymappings
@ -13135,7 +13135,7 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
description: A UUID string identifying this Notification Webhook Mapping. description: A UUID string identifying this Webhook Mapping.
required: true required: true
tags: tags:
- propertymappings - propertymappings
@ -13175,7 +13175,7 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
description: A UUID string identifying this Notification Webhook Mapping. description: A UUID string identifying this Webhook Mapping.
required: true required: true
tags: tags:
- propertymappings - propertymappings
@ -13214,7 +13214,7 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
description: A UUID string identifying this Notification Webhook Mapping. description: A UUID string identifying this Webhook Mapping.
required: true required: true
tags: tags:
- propertymappings - propertymappings
@ -13245,7 +13245,7 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
description: A UUID string identifying this Notification Webhook Mapping. description: A UUID string identifying this Webhook Mapping.
required: true required: true
tags: tags:
- propertymappings - propertymappings
@ -18794,6 +18794,11 @@ paths:
name: from_number name: from_number
schema: schema:
type: string type: string
- in: query
name: mapping
schema:
type: string
format: uuid
- in: query - in: query
name: name name: name
schema: schema:
@ -25185,6 +25190,11 @@ components:
description: When enabled, the Phone number is only used during enrollment 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 verify the users authenticity. Only a hash of the phone number is saved
to ensure it is not re-used in the future. 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: required:
- account_sid - account_sid
- auth - auth
@ -25233,6 +25243,11 @@ components:
description: When enabled, the Phone number is only used during enrollment 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 verify the users authenticity. Only a hash of the phone number is saved
to ensure it is not re-used in the future. 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: required:
- account_sid - account_sid
- auth - auth
@ -32702,6 +32717,11 @@ components:
description: When enabled, the Phone number is only used during enrollment 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 verify the users authenticity. Only a hash of the phone number is saved
to ensure it is not re-used in the future. 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: PatchedAuthenticatorStaticStageRequest:
type: object type: object
description: AuthenticatorStaticStage Serializer description: AuthenticatorStaticStage Serializer

View File

@ -16,6 +16,7 @@ import {
AuthenticatorSMSStage, AuthenticatorSMSStage,
FlowsApi, FlowsApi,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
PropertymappingsApi,
ProviderEnum, ProviderEnum,
StagesApi, StagesApi,
} from "@goauthentik/api"; } from "@goauthentik/api";
@ -91,7 +92,8 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
} }
renderProviderGeneric(): TemplateResult { renderProviderGeneric(): TemplateResult {
return html` <ak-form-element-horizontal return html`
<ak-form-element-horizontal
label=${t`Authentication Type`} label=${t`Authentication Type`}
@change=${(ev: Event) => { @change=${(ev: Event) => {
const current = (ev.target as HTMLInputElement).value; const current = (ev.target as HTMLInputElement).value;
@ -153,7 +155,33 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${t`This is the password to be used with basic auth`} ${t`This is the password to be used with basic auth`}
</p> </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 { 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. 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 ## Verify only
:::info :::info