events: add send_once flag to send webhooks only once
This commit is contained in:
parent
3ac148d01c
commit
cfed41439e
|
@ -15,6 +15,7 @@ class NotificationTransportForm(forms.ModelForm):
|
||||||
"name",
|
"name",
|
||||||
"mode",
|
"mode",
|
||||||
"webhook_url",
|
"webhook_url",
|
||||||
|
"send_once",
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Generated by Django 3.1.6 on 2021-02-02 18:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_events", "0011_notification_rules_default_v1"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="notificationtransport",
|
||||||
|
name="send_once",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Only send notification once, for example when sending a webhook into a chat channel.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="event",
|
||||||
|
name="action",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
("login", "Login"),
|
||||||
|
("login_failed", "Login Failed"),
|
||||||
|
("logout", "Logout"),
|
||||||
|
("user_write", "User Write"),
|
||||||
|
("suspicious_request", "Suspicious Request"),
|
||||||
|
("password_set", "Password Set"),
|
||||||
|
("token_view", "Token View"),
|
||||||
|
("invitation_used", "Invite Used"),
|
||||||
|
("authorize_application", "Authorize Application"),
|
||||||
|
("source_linked", "Source Linked"),
|
||||||
|
("impersonation_started", "Impersonation Started"),
|
||||||
|
("impersonation_ended", "Impersonation Ended"),
|
||||||
|
("policy_execution", "Policy Execution"),
|
||||||
|
("policy_exception", "Policy Exception"),
|
||||||
|
("property_mapping_exception", "Property Mapping Exception"),
|
||||||
|
("system_task_execution", "System Task Execution"),
|
||||||
|
("system_task_exception", "System Task Exception"),
|
||||||
|
("configuration_error", "Configuration Error"),
|
||||||
|
("model_created", "Model Created"),
|
||||||
|
("model_updated", "Model Updated"),
|
||||||
|
("model_deleted", "Model Deleted"),
|
||||||
|
("update_available", "Update Available"),
|
||||||
|
("custom_", "Custom Prefix"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -184,6 +184,12 @@ class NotificationTransport(models.Model):
|
||||||
mode = models.TextField(choices=TransportMode.choices)
|
mode = models.TextField(choices=TransportMode.choices)
|
||||||
|
|
||||||
webhook_url = models.TextField(blank=True)
|
webhook_url = models.TextField(blank=True)
|
||||||
|
send_once = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_(
|
||||||
|
"Only send notification once, for example when sending a webhook into a chat channel."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
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"""
|
||||||
|
|
|
@ -65,15 +65,17 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
||||||
|
|
||||||
LOGGER.debug("e(trigger): event trigger matched", trigger=trigger)
|
LOGGER.debug("e(trigger): event trigger matched", trigger=trigger)
|
||||||
# Create the notification objects
|
# Create the notification objects
|
||||||
|
for transport in trigger.transports.all():
|
||||||
for user in trigger.group.users.all():
|
for user in trigger.group.users.all():
|
||||||
|
LOGGER.debug("created notif")
|
||||||
notification = Notification.objects.create(
|
notification = Notification.objects.create(
|
||||||
severity=trigger.severity, body=event.summary, event=event, user=user
|
severity=trigger.severity, body=event.summary, event=event, user=user
|
||||||
)
|
)
|
||||||
|
|
||||||
for transport in trigger.transports.all():
|
|
||||||
notification_transport.apply_async(
|
notification_transport.apply_async(
|
||||||
args=[notification.pk, transport.pk], queue="authentik_events"
|
args=[notification.pk, transport.pk], queue="authentik_events"
|
||||||
)
|
)
|
||||||
|
if transport.send_once:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task(
|
@CELERY_APP.task(
|
||||||
|
|
|
@ -8,6 +8,7 @@ from authentik.core.models import Group, User
|
||||||
from authentik.events.models import (
|
from authentik.events.models import (
|
||||||
Event,
|
Event,
|
||||||
EventAction,
|
EventAction,
|
||||||
|
Notification,
|
||||||
NotificationRule,
|
NotificationRule,
|
||||||
NotificationTransport,
|
NotificationTransport,
|
||||||
)
|
)
|
||||||
|
@ -21,7 +22,7 @@ class TestEventsNotifications(TestCase):
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.group = Group.objects.create(name="test-group")
|
self.group = Group.objects.create(name="test-group")
|
||||||
self.user = User.objects.create(name="test-user")
|
self.user = User.objects.create(name="test-user", username="test")
|
||||||
self.group.users.add(self.user)
|
self.group.users.add(self.user)
|
||||||
self.group.save()
|
self.group.save()
|
||||||
|
|
||||||
|
@ -88,3 +89,26 @@ class TestEventsNotifications(TestCase):
|
||||||
):
|
):
|
||||||
Event.new(EventAction.CUSTOM_PREFIX).save()
|
Event.new(EventAction.CUSTOM_PREFIX).save()
|
||||||
self.assertEqual(passes.call_count, 1)
|
self.assertEqual(passes.call_count, 1)
|
||||||
|
|
||||||
|
def test_transport_once(self):
|
||||||
|
"""Test transport's send_once"""
|
||||||
|
user2 = User.objects.create(name="test2-user", username="test2")
|
||||||
|
self.group.users.add(user2)
|
||||||
|
self.group.save()
|
||||||
|
|
||||||
|
transport = NotificationTransport.objects.create(
|
||||||
|
name="transport", send_once=True
|
||||||
|
)
|
||||||
|
NotificationRule.objects.filter(name__startswith="default").delete()
|
||||||
|
trigger = NotificationRule.objects.create(name="trigger", group=self.group)
|
||||||
|
trigger.transports.add(transport)
|
||||||
|
trigger.save()
|
||||||
|
matcher = EventMatcherPolicy.objects.create(
|
||||||
|
name="matcher", action=EventAction.CUSTOM_PREFIX
|
||||||
|
)
|
||||||
|
PolicyBinding.objects.create(target=trigger, policy=matcher, order=0)
|
||||||
|
|
||||||
|
execute_mock = MagicMock()
|
||||||
|
with patch("authentik.events.models.NotificationTransport.send", execute_mock):
|
||||||
|
Event.new(EventAction.CUSTOM_PREFIX).save()
|
||||||
|
self.assertEqual(Notification.objects.count(), 1)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Generated by Django 3.1.6 on 2021-02-02 18:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_policies_event_matcher", "0004_auto_20210112_2158"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="eventmatcherpolicy",
|
||||||
|
name="action",
|
||||||
|
field=models.TextField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("login", "Login"),
|
||||||
|
("login_failed", "Login Failed"),
|
||||||
|
("logout", "Logout"),
|
||||||
|
("user_write", "User Write"),
|
||||||
|
("suspicious_request", "Suspicious Request"),
|
||||||
|
("password_set", "Password Set"),
|
||||||
|
("token_view", "Token View"),
|
||||||
|
("invitation_used", "Invite Used"),
|
||||||
|
("authorize_application", "Authorize Application"),
|
||||||
|
("source_linked", "Source Linked"),
|
||||||
|
("impersonation_started", "Impersonation Started"),
|
||||||
|
("impersonation_ended", "Impersonation Ended"),
|
||||||
|
("policy_execution", "Policy Execution"),
|
||||||
|
("policy_exception", "Policy Exception"),
|
||||||
|
("property_mapping_exception", "Property Mapping Exception"),
|
||||||
|
("system_task_execution", "System Task Execution"),
|
||||||
|
("system_task_exception", "System Task Exception"),
|
||||||
|
("configuration_error", "Configuration Error"),
|
||||||
|
("model_created", "Model Created"),
|
||||||
|
("model_updated", "Model Updated"),
|
||||||
|
("model_deleted", "Model Deleted"),
|
||||||
|
("update_available", "Update Available"),
|
||||||
|
("custom_", "Custom Prefix"),
|
||||||
|
],
|
||||||
|
help_text="Match created events with this action type. When left empty, all action types will be matched.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7701,6 +7701,11 @@ definitions:
|
||||||
webhook_url:
|
webhook_url:
|
||||||
title: Webhook url
|
title: Webhook url
|
||||||
type: string
|
type: string
|
||||||
|
send_once:
|
||||||
|
title: Send once
|
||||||
|
description: Only send notification once, for example when sending a
|
||||||
|
webhook into a chat channel.
|
||||||
|
type: boolean
|
||||||
readOnly: true
|
readOnly: true
|
||||||
severity:
|
severity:
|
||||||
title: Severity
|
title: Severity
|
||||||
|
|
Reference in New Issue