events: add local transport mode (#2992)
* events: add local transport mode Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add default local transport Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
fc75867218
commit
8faa1bf865
|
@ -0,0 +1,49 @@
|
||||||
|
# Generated by Django 4.0.4 on 2022-05-30 18:08
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
from authentik.events.models import TransportMode
|
||||||
|
|
||||||
|
|
||||||
|
def notify_local_transport(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||||
|
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||||
|
|
||||||
|
local_transport, _ = NotificationTransport.objects.using(db_alias).update_or_create(
|
||||||
|
name="default-local-transport",
|
||||||
|
defaults={"mode": TransportMode.LOCAL},
|
||||||
|
)
|
||||||
|
|
||||||
|
for trigger in NotificationRule.objects.using(db_alias).filter(
|
||||||
|
name__in=[
|
||||||
|
"default-notify-configuration-error",
|
||||||
|
"default-notify-exception",
|
||||||
|
"default-notify-update",
|
||||||
|
]
|
||||||
|
):
|
||||||
|
trigger.transports.add(local_transport)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_events", "0001_squashed_0019_alter_notificationtransport_webhook_url"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notificationtransport",
|
||||||
|
name="mode",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
("local", "authentik inbuilt notifications"),
|
||||||
|
("webhook", "Generic Webhook"),
|
||||||
|
("webhook_slack", "Slack Webhook (Slack/Discord)"),
|
||||||
|
("email", "Email"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(notify_local_transport),
|
||||||
|
]
|
|
@ -289,6 +289,7 @@ class Event(ExpiringModel):
|
||||||
class TransportMode(models.TextChoices):
|
class TransportMode(models.TextChoices):
|
||||||
"""Modes that a notification transport can send a notification"""
|
"""Modes that a notification transport can send a notification"""
|
||||||
|
|
||||||
|
LOCAL = "local", _("authentik inbuilt notifications")
|
||||||
WEBHOOK = "webhook", _("Generic Webhook")
|
WEBHOOK = "webhook", _("Generic Webhook")
|
||||||
WEBHOOK_SLACK = "webhook_slack", _("Slack Webhook (Slack/Discord)")
|
WEBHOOK_SLACK = "webhook_slack", _("Slack Webhook (Slack/Discord)")
|
||||||
EMAIL = "email", _("Email")
|
EMAIL = "email", _("Email")
|
||||||
|
@ -315,6 +316,8 @@ class NotificationTransport(models.Model):
|
||||||
|
|
||||||
def send(self, notification: "Notification") -> list[str]:
|
def send(self, notification: "Notification") -> list[str]:
|
||||||
"""Send notification to user, called from async task"""
|
"""Send notification to user, called from async task"""
|
||||||
|
if self.mode == TransportMode.LOCAL:
|
||||||
|
return self.send_local(notification)
|
||||||
if self.mode == TransportMode.WEBHOOK:
|
if self.mode == TransportMode.WEBHOOK:
|
||||||
return self.send_webhook(notification)
|
return self.send_webhook(notification)
|
||||||
if self.mode == TransportMode.WEBHOOK_SLACK:
|
if self.mode == TransportMode.WEBHOOK_SLACK:
|
||||||
|
@ -323,6 +326,17 @@ class NotificationTransport(models.Model):
|
||||||
return self.send_email(notification)
|
return self.send_email(notification)
|
||||||
raise ValueError(f"Invalid mode {self.mode} set")
|
raise ValueError(f"Invalid mode {self.mode} set")
|
||||||
|
|
||||||
|
def send_local(self, notification: "Notification") -> list[str]:
|
||||||
|
"""Local notification delivery"""
|
||||||
|
if self.webhook_mapping:
|
||||||
|
self.webhook_mapping.evaluate(
|
||||||
|
user=notification.user,
|
||||||
|
request=None,
|
||||||
|
notification=notification,
|
||||||
|
)
|
||||||
|
notification.save()
|
||||||
|
return []
|
||||||
|
|
||||||
def send_webhook(self, notification: "Notification") -> list[str]:
|
def send_webhook(self, notification: "Notification") -> list[str]:
|
||||||
"""Send notification to generic webhook"""
|
"""Send notification to generic webhook"""
|
||||||
default_body = {
|
default_body = {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
"""Event notification tasks"""
|
"""Event notification tasks"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from django.db.models.query_utils import Q
|
from django.db.models.query_utils import Q
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.events.models import (
|
from authentik.events.models import (
|
||||||
Event,
|
Event,
|
||||||
|
@ -39,10 +42,9 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
||||||
LOGGER.warning("event doesn't exist yet or anymore", event_uuid=event_uuid)
|
LOGGER.warning("event doesn't exist yet or anymore", event_uuid=event_uuid)
|
||||||
return
|
return
|
||||||
event: Event = events.first()
|
event: Event = events.first()
|
||||||
triggers: NotificationRule = NotificationRule.objects.filter(name=trigger_name)
|
trigger: Optional[NotificationRule] = NotificationRule.objects.filter(name=trigger_name).first()
|
||||||
if not triggers.exists():
|
if not trigger:
|
||||||
return
|
return
|
||||||
trigger = triggers.first()
|
|
||||||
|
|
||||||
if "policy_uuid" in event.context:
|
if "policy_uuid" in event.context:
|
||||||
policy_uuid = event.context["policy_uuid"]
|
policy_uuid = event.context["policy_uuid"]
|
||||||
|
@ -81,11 +83,14 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
||||||
for transport in trigger.transports.all():
|
for transport in trigger.transports.all():
|
||||||
for user in trigger.group.users.all():
|
for user in trigger.group.users.all():
|
||||||
LOGGER.debug("created notification")
|
LOGGER.debug("created notification")
|
||||||
notification = Notification.objects.create(
|
|
||||||
severity=trigger.severity, body=event.summary, event=event, user=user
|
|
||||||
)
|
|
||||||
notification_transport.apply_async(
|
notification_transport.apply_async(
|
||||||
args=[notification.pk, transport.pk], queue="authentik_events"
|
args=[
|
||||||
|
transport.pk,
|
||||||
|
str(event.pk),
|
||||||
|
user.pk,
|
||||||
|
str(trigger.pk),
|
||||||
|
],
|
||||||
|
queue="authentik_events",
|
||||||
)
|
)
|
||||||
if transport.send_once:
|
if transport.send_once:
|
||||||
break
|
break
|
||||||
|
@ -97,19 +102,30 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
||||||
retry_backoff=True,
|
retry_backoff=True,
|
||||||
base=MonitoredTask,
|
base=MonitoredTask,
|
||||||
)
|
)
|
||||||
def notification_transport(self: MonitoredTask, notification_pk: int, transport_pk: int):
|
def notification_transport(
|
||||||
|
self: MonitoredTask, transport_pk: int, event_pk: str, user_pk: int, trigger_pk: str
|
||||||
|
):
|
||||||
"""Send notification over specified transport"""
|
"""Send notification over specified transport"""
|
||||||
self.save_on_success = False
|
self.save_on_success = False
|
||||||
try:
|
try:
|
||||||
notification: Notification = Notification.objects.filter(pk=notification_pk).first()
|
event = Event.objects.filter(pk=event_pk).first()
|
||||||
if not notification:
|
if not event:
|
||||||
return
|
return
|
||||||
|
user = User.objects.filter(pk=user_pk).first()
|
||||||
|
if not user:
|
||||||
|
return
|
||||||
|
trigger = NotificationRule.objects.filter(pk=trigger_pk).first()
|
||||||
|
if not trigger:
|
||||||
|
return
|
||||||
|
notification = Notification(
|
||||||
|
severity=trigger.severity, body=event.summary, event=event, user=user
|
||||||
|
)
|
||||||
transport = NotificationTransport.objects.filter(pk=transport_pk).first()
|
transport = NotificationTransport.objects.filter(pk=transport_pk).first()
|
||||||
if not transport:
|
if not transport:
|
||||||
return
|
return
|
||||||
transport.send(notification)
|
transport.send(notification)
|
||||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))
|
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))
|
||||||
except NotificationTransportError as exc:
|
except (NotificationTransportError, PropertyMappingExpressionException) as exc:
|
||||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,10 @@ from authentik.events.models import (
|
||||||
Notification,
|
Notification,
|
||||||
NotificationRule,
|
NotificationRule,
|
||||||
NotificationTransport,
|
NotificationTransport,
|
||||||
|
NotificationWebhookMapping,
|
||||||
|
TransportMode,
|
||||||
)
|
)
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.policies.event_matcher.models import EventMatcherPolicy
|
from authentik.policies.event_matcher.models import EventMatcherPolicy
|
||||||
from authentik.policies.exceptions import PolicyException
|
from authentik.policies.exceptions import PolicyException
|
||||||
from authentik.policies.models import PolicyBinding
|
from authentik.policies.models import PolicyBinding
|
||||||
|
@ -105,4 +108,26 @@ class TestEventsNotifications(TestCase):
|
||||||
execute_mock = MagicMock()
|
execute_mock = MagicMock()
|
||||||
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
|
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
|
||||||
Event.new(EventAction.CUSTOM_PREFIX).save()
|
Event.new(EventAction.CUSTOM_PREFIX).save()
|
||||||
self.assertEqual(Notification.objects.count(), 1)
|
self.assertEqual(execute_mock.call_count, 1)
|
||||||
|
|
||||||
|
def test_transport_mapping(self):
|
||||||
|
"""Test transport mapping"""
|
||||||
|
mapping = NotificationWebhookMapping.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
expression="""notification.body = 'foo'""",
|
||||||
|
)
|
||||||
|
|
||||||
|
transport = NotificationTransport.objects.create(
|
||||||
|
name="transport", webhook_mapping=mapping, mode=TransportMode.LOCAL
|
||||||
|
)
|
||||||
|
NotificationRule.objects.filter(name__startswith="default").delete()
|
||||||
|
trigger = NotificationRule.objects.create(name="trigger", group=self.group)
|
||||||
|
trigger.transports.add(transport)
|
||||||
|
matcher = EventMatcherPolicy.objects.create(
|
||||||
|
name="matcher", action=EventAction.CUSTOM_PREFIX
|
||||||
|
)
|
||||||
|
PolicyBinding.objects.create(target=trigger, policy=matcher, order=0)
|
||||||
|
|
||||||
|
Notification.objects.all().delete()
|
||||||
|
Event.new(EventAction.CUSTOM_PREFIX).save()
|
||||||
|
self.assertEqual(Notification.objects.first().body, "foo")
|
||||||
|
|
|
@ -4490,6 +4490,7 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- email
|
- email
|
||||||
|
- local
|
||||||
- webhook
|
- webhook
|
||||||
- webhook_slack
|
- webhook_slack
|
||||||
- in: query
|
- in: query
|
||||||
|
@ -23047,6 +23048,7 @@ components:
|
||||||
- pk
|
- pk
|
||||||
NotificationTransportModeEnum:
|
NotificationTransportModeEnum:
|
||||||
enum:
|
enum:
|
||||||
|
- local
|
||||||
- webhook
|
- webhook
|
||||||
- webhook_slack
|
- webhook_slack
|
||||||
- email
|
- email
|
||||||
|
|
|
@ -2973,6 +2973,10 @@ msgstr "Lädt..."
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokal"
|
msgstr "Lokal"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "Gebietsschema"
|
#~ msgstr "Gebietsschema"
|
||||||
|
|
|
@ -3025,6 +3025,10 @@ msgstr "Loading..."
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Local"
|
msgstr "Local"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr "Local (notifications will be created within authentik)"
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "Locale"
|
#~ msgstr "Locale"
|
||||||
|
|
|
@ -2966,6 +2966,10 @@ msgstr "Cargando..."
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Local"
|
msgstr "Local"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "Lugar"
|
#~ msgstr "Lugar"
|
||||||
|
|
|
@ -2997,6 +2997,10 @@ msgstr "Chargement en cours..."
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Local"
|
msgstr "Local"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
|
@ -2963,6 +2963,10 @@ msgstr "Ładowanie..."
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Lokalny"
|
msgstr "Lokalny"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "Język"
|
#~ msgstr "Język"
|
||||||
|
|
|
@ -3007,6 +3007,10 @@ msgstr ""
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr ""
|
#~ msgstr ""
|
||||||
|
|
|
@ -2967,6 +2967,10 @@ msgstr "Yükleniyor..."
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "Yerel"
|
msgstr "Yerel"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "Yerelleştirme"
|
#~ msgstr "Yerelleştirme"
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2953,6 +2953,10 @@ msgstr "载入中……"
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "本地"
|
msgstr "本地"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "区域设置"
|
#~ msgstr "区域设置"
|
||||||
|
|
||||||
|
|
|
@ -2953,6 +2953,10 @@ msgstr "载入中……"
|
||||||
msgid "Local"
|
msgid "Local"
|
||||||
msgstr "本地"
|
msgstr "本地"
|
||||||
|
|
||||||
|
#: src/pages/events/TransportForm.ts
|
||||||
|
msgid "Local (notifications will be created within authentik)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#~ msgid "Locale"
|
#~ msgid "Locale"
|
||||||
#~ msgstr "区域设置"
|
#~ msgstr "区域设置"
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,12 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||||
|
|
||||||
renderTransportModes(): TemplateResult {
|
renderTransportModes(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
|
<option
|
||||||
|
value=${NotificationTransportModeEnum.Local}
|
||||||
|
?selected=${this.instance?.mode === NotificationTransportModeEnum.Local}
|
||||||
|
>
|
||||||
|
${t`Local (notifications will be created within authentik)`}
|
||||||
|
</option>
|
||||||
<option
|
<option
|
||||||
value=${NotificationTransportModeEnum.Email}
|
value=${NotificationTransportModeEnum.Email}
|
||||||
?selected=${this.instance?.mode === NotificationTransportModeEnum.Email}
|
?selected=${this.instance?.mode === NotificationTransportModeEnum.Email}
|
||||||
|
|
Reference in a new issue