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",
|
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):
|
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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -27,6 +27,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
|
||||||
"auth_password",
|
"auth_password",
|
||||||
"auth_type",
|
"auth_type",
|
||||||
"verify_only",
|
"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 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:
|
||||||
|
|
30
schema.yml
30
schema.yml
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in New Issue