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:
		
							
								
								
									
										80
									
								
								authentik/events/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								authentik/events/tasks.py
									
									
									
									
									
										Normal 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 | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L