events: Notifications (#418)

* events: initial alerting implementation

* policies: move error handling to process, ensure policy UUID is saved

* policies: add tests for error handling in PolicyProcess

* events: improve loop detection

* events: add API for action and trigger

* policies: ensure http_request is not used in context

* events: adjust unittests for user handling

* policies/event_matcher: add policy type

* events: add API tests

* events: add middleware tests

* core: make application's provider not required

* outposts: allow blank kubeconfig

* outposts: validate kubeconfig before saving

* api: fix formatting

* stages/invitation: remove invitation_created signal as model_created functions the same

* stages/invitation: ensure created_by is set when creating from API

* events: rebase migrations on master

* events: fix missing Alerts from API

* policies: fix unittests

* events: add tests for alerts

* events: rename from alerting to notifications

* events: add ability to specify severity of notification created

* policies/event_matcher: Add app field to match on event app

* policies/event_matcher: fix EventMatcher not being included in API

* core: use objects.none() when get_queryset is used

* events: use m2m for multiple transports, create notification object in task

* events: add default triggers

* events: fix migrations return value

* events: fix notification_transport not being in the correct queue

* stages/email: allow sending of email without backend

* events: implement sending via webhook + slack/discord + email
This commit is contained in:
Jens L
2021-01-11 18:43:59 +01:00
committed by GitHub
parent f8a426f0e8
commit 1ccf6dcf6f
40 changed files with 2074 additions and 84 deletions

80
authentik/events/tasks.py Normal file
View File

@ -0,0 +1,80 @@
"""Event notification tasks"""
from guardian.shortcuts import get_anonymous_user
from structlog import get_logger
from authentik.events.models import (
Event,
Notification,
NotificationTransport,
NotificationTrigger,
)
from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.policies.engine import PolicyEngine
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()
@CELERY_APP.task()
def event_notification_handler(event_uuid: str):
"""Start task for each trigger definition"""
for trigger in NotificationTrigger.objects.all():
event_trigger_handler.apply_async(
args=[event_uuid, trigger.name], queue="authentik_events"
)
@CELERY_APP.task()
def event_trigger_handler(event_uuid: str, trigger_name: str):
"""Check if policies attached to NotificationTrigger match event"""
event: Event = Event.objects.get(event_uuid=event_uuid)
trigger: NotificationTrigger = NotificationTrigger.objects.get(name=trigger_name)
if "policy_uuid" in event.context:
policy_uuid = event.context["policy_uuid"]
if trigger.policies.filter(policy_uuid=policy_uuid).exists():
# Event has been created by a policy that is attached
# to this trigger. To prevent infinite loops, we stop here
LOGGER.debug("e(trigger): attempting to prevent infinite loop")
return
if not trigger.group:
LOGGER.debug("e(trigger): trigger has no group")
return
policy_engine = PolicyEngine(trigger, get_anonymous_user())
policy_engine.request.context["event"] = event
policy_engine.build()
result = policy_engine.result
if not result.passing:
return
LOGGER.debug("e(trigger): event trigger matched")
# Create the notification objects
for user in trigger.group.users.all():
notification = Notification.objects.create(
severity=trigger.severity, body=event.summary, event=event, user=user
)
for transport in trigger.transports.all():
notification_transport.apply_async(
args=[notification.pk, transport.pk], queue="authentik_events"
)
@CELERY_APP.task(bind=True, base=MonitoredTask)
def notification_transport(
self: MonitoredTask, notification_pk: int, transport_pk: int
):
"""Send notification over specified transport"""
self.save_on_success = False
try:
notification: Notification = Notification.objects.get(pk=notification_pk)
transport: NotificationTransport = NotificationTransport.objects.get(
pk=transport_pk
)
transport.send(notification)
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL))
except Exception as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
raise exc