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):
|
||||
"""Modes that a notification transport can send a notification"""
|
||||
|
||||
LOCAL = "local", _("authentik inbuilt notifications")
|
||||
WEBHOOK = "webhook", _("Generic Webhook")
|
||||
WEBHOOK_SLACK = "webhook_slack", _("Slack Webhook (Slack/Discord)")
|
||||
EMAIL = "email", _("Email")
|
||||
|
@ -315,6 +316,8 @@ class NotificationTransport(models.Model):
|
|||
|
||||
def send(self, notification: "Notification") -> list[str]:
|
||||
"""Send notification to user, called from async task"""
|
||||
if self.mode == TransportMode.LOCAL:
|
||||
return self.send_local(notification)
|
||||
if self.mode == TransportMode.WEBHOOK:
|
||||
return self.send_webhook(notification)
|
||||
if self.mode == TransportMode.WEBHOOK_SLACK:
|
||||
|
@ -323,6 +326,17 @@ class NotificationTransport(models.Model):
|
|||
return self.send_email(notification)
|
||||
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]:
|
||||
"""Send notification to generic webhook"""
|
||||
default_body = {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
"""Event notification tasks"""
|
||||
from typing import Optional
|
||||
|
||||
from django.db.models.query_utils import Q
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import (
|
||||
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)
|
||||
return
|
||||
event: Event = events.first()
|
||||
triggers: NotificationRule = NotificationRule.objects.filter(name=trigger_name)
|
||||
if not triggers.exists():
|
||||
trigger: Optional[NotificationRule] = NotificationRule.objects.filter(name=trigger_name).first()
|
||||
if not trigger:
|
||||
return
|
||||
trigger = triggers.first()
|
||||
|
||||
if "policy_uuid" in event.context:
|
||||
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 user in trigger.group.users.all():
|
||||
LOGGER.debug("created notification")
|
||||
notification = Notification.objects.create(
|
||||
severity=trigger.severity, body=event.summary, event=event, user=user
|
||||
)
|
||||
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:
|
||||
break
|
||||
|
@ -97,19 +102,30 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
|||
retry_backoff=True,
|
||||
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"""
|
||||
self.save_on_success = False
|
||||
try:
|
||||
notification: Notification = Notification.objects.filter(pk=notification_pk).first()
|
||||
if not notification:
|
||||
event = Event.objects.filter(pk=event_pk).first()
|
||||
if not event:
|
||||
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()
|
||||
if not transport:
|
||||
return
|
||||
transport.send(notification)
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))
|
||||
except NotificationTransportError as exc:
|
||||
except (NotificationTransportError, PropertyMappingExpressionException) as exc:
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
raise exc
|
||||
|
||||
|
|
|
@ -11,7 +11,10 @@ from authentik.events.models import (
|
|||
Notification,
|
||||
NotificationRule,
|
||||
NotificationTransport,
|
||||
NotificationWebhookMapping,
|
||||
TransportMode,
|
||||
)
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.event_matcher.models import EventMatcherPolicy
|
||||
from authentik.policies.exceptions import PolicyException
|
||||
from authentik.policies.models import PolicyBinding
|
||||
|
@ -105,4 +108,26 @@ class TestEventsNotifications(TestCase):
|
|||
execute_mock = MagicMock()
|
||||
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
|
||||
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
|
||||
enum:
|
||||
- email
|
||||
- local
|
||||
- webhook
|
||||
- webhook_slack
|
||||
- in: query
|
||||
|
@ -23047,6 +23048,7 @@ components:
|
|||
- pk
|
||||
NotificationTransportModeEnum:
|
||||
enum:
|
||||
- local
|
||||
- webhook
|
||||
- webhook_slack
|
||||
- email
|
||||
|
|
|
@ -2973,6 +2973,10 @@ msgstr "Lädt..."
|
|||
msgid "Local"
|
||||
msgstr "Lokal"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "Gebietsschema"
|
||||
|
|
|
@ -3025,6 +3025,10 @@ msgstr "Loading..."
|
|||
msgid "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
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "Locale"
|
||||
|
|
|
@ -2966,6 +2966,10 @@ msgstr "Cargando..."
|
|||
msgid "Local"
|
||||
msgstr "Local"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "Lugar"
|
||||
|
|
|
@ -2997,6 +2997,10 @@ msgstr "Chargement en cours..."
|
|||
msgid "Local"
|
||||
msgstr "Local"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr ""
|
||||
|
|
|
@ -2963,6 +2963,10 @@ msgstr "Ładowanie..."
|
|||
msgid "Local"
|
||||
msgstr "Lokalny"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "Język"
|
||||
|
|
|
@ -3007,6 +3007,10 @@ msgstr ""
|
|||
msgid "Local"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr ""
|
||||
|
|
|
@ -2967,6 +2967,10 @@ msgstr "Yükleniyor..."
|
|||
msgid "Local"
|
||||
msgstr "Yerel"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#: src/user/user-settings/details/UserDetailsForm.ts
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "Yerelleştirme"
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2953,6 +2953,10 @@ msgstr "载入中……"
|
|||
msgid "Local"
|
||||
msgstr "本地"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "区域设置"
|
||||
|
||||
|
|
|
@ -2953,6 +2953,10 @@ msgstr "载入中……"
|
|||
msgid "Local"
|
||||
msgstr "本地"
|
||||
|
||||
#: src/pages/events/TransportForm.ts
|
||||
msgid "Local (notifications will be created within authentik)"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Locale"
|
||||
#~ msgstr "区域设置"
|
||||
|
||||
|
|
|
@ -56,6 +56,12 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
|||
|
||||
renderTransportModes(): TemplateResult {
|
||||
return html`
|
||||
<option
|
||||
value=${NotificationTransportModeEnum.Local}
|
||||
?selected=${this.instance?.mode === NotificationTransportModeEnum.Local}
|
||||
>
|
||||
${t`Local (notifications will be created within authentik)`}
|
||||
</option>
|
||||
<option
|
||||
value=${NotificationTransportModeEnum.Email}
|
||||
?selected=${this.instance?.mode === NotificationTransportModeEnum.Email}
|
||||
|
|
Reference in a new issue