events: catch errors during send and re-raise as custom type
This commit is contained in:
		| @ -1,6 +1,6 @@ | |||||||
| """authentik events models""" | """authentik events models""" | ||||||
|  |  | ||||||
| from inspect import getmodule, stack | from inspect import getmodule, stack | ||||||
|  | from smtplib import SMTPException | ||||||
| from typing import Optional, Union | from typing import Optional, Union | ||||||
| from uuid import uuid4 | from uuid import uuid4 | ||||||
|  |  | ||||||
| @ -9,7 +9,7 @@ from django.core.exceptions import ValidationError | |||||||
| from django.db import models | from django.db import models | ||||||
| from django.http import HttpRequest | from django.http import HttpRequest | ||||||
| from django.utils.translation import gettext as _ | from django.utils.translation import gettext as _ | ||||||
| from requests import post | from requests import RequestException, post | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik import __version__ | from authentik import __version__ | ||||||
| @ -19,6 +19,7 @@ from authentik.core.middleware import ( | |||||||
| ) | ) | ||||||
| from authentik.core.models import Group, User | from authentik.core.models import Group, User | ||||||
| from authentik.events.utils import cleanse_dict, get_user, sanitize_dict | from authentik.events.utils import cleanse_dict, get_user, sanitize_dict | ||||||
|  | from authentik.lib.sentry import SentryIgnoredException | ||||||
| from authentik.lib.utils.http import get_client_ip | from authentik.lib.utils.http import get_client_ip | ||||||
| from authentik.policies.models import PolicyBindingModel | from authentik.policies.models import PolicyBindingModel | ||||||
| from authentik.stages.email.tasks import send_mail | from authentik.stages.email.tasks import send_mail | ||||||
| @ -27,6 +28,10 @@ from authentik.stages.email.utils import TemplateEmailMessage | |||||||
| LOGGER = get_logger("authentik.events") | LOGGER = get_logger("authentik.events") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NotificationTransportError(SentryIgnoredException): | ||||||
|  |     """Error raised when a notification fails to be delivered""" | ||||||
|  |  | ||||||
|  |  | ||||||
| class EventAction(models.TextChoices): | class EventAction(models.TextChoices): | ||||||
|     """All possible actions to save into the events log""" |     """All possible actions to save into the events log""" | ||||||
|  |  | ||||||
| @ -192,13 +197,17 @@ class NotificationTransport(models.Model): | |||||||
|  |  | ||||||
|     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""" | ||||||
|         response = post( |         try: | ||||||
|             self.webhook_url, |             response = post( | ||||||
|             json={ |                 self.webhook_url, | ||||||
|                 "body": notification.body, |                 json={ | ||||||
|                 "severity": notification.severity, |                     "body": notification.body, | ||||||
|             }, |                     "severity": notification.severity, | ||||||
|         ) |                 }, | ||||||
|  |             ) | ||||||
|  |             response.raise_for_status() | ||||||
|  |         except RequestException as exc: | ||||||
|  |             raise NotificationTransportError from exc | ||||||
|         return [ |         return [ | ||||||
|             response.status_code, |             response.status_code, | ||||||
|             response.text, |             response.text, | ||||||
| @ -235,7 +244,11 @@ class NotificationTransport(models.Model): | |||||||
|         if notification.event: |         if notification.event: | ||||||
|             body["attachments"][0]["title"] = notification.event.action |             body["attachments"][0]["title"] = notification.event.action | ||||||
|             body["attachments"][0]["text"] = notification.event.action |             body["attachments"][0]["text"] = notification.event.action | ||||||
|         response = post(self.webhook_url, json=body) |         try: | ||||||
|  |             response = post(self.webhook_url, json=body) | ||||||
|  |             response.raise_for_status() | ||||||
|  |         except RequestException as exc: | ||||||
|  |             raise NotificationTransportError from exc | ||||||
|         return [ |         return [ | ||||||
|             response.status_code, |             response.status_code, | ||||||
|             response.text, |             response.text, | ||||||
| @ -257,8 +270,11 @@ class NotificationTransport(models.Model): | |||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         # Email is sent directly here, as the call to send() should have been from a task. |         # Email is sent directly here, as the call to send() should have been from a task. | ||||||
|         # pyright: reportGeneralTypeIssues=false |         try: | ||||||
|         return send_mail(mail.__dict__)  # pylint: disable=no-value-for-parameter |             # pyright: reportGeneralTypeIssues=false | ||||||
|  |             return send_mail(mail.__dict__)  # pylint: disable=no-value-for-parameter | ||||||
|  |         except (SMTPException, ConnectionError) as exc: | ||||||
|  |             raise NotificationTransportError from exc | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ from authentik.events.models import ( | |||||||
|     Event, |     Event, | ||||||
|     Notification, |     Notification, | ||||||
|     NotificationTransport, |     NotificationTransport, | ||||||
|  |     NotificationTransportError, | ||||||
|     NotificationTrigger, |     NotificationTrigger, | ||||||
| ) | ) | ||||||
| from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus | from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus | ||||||
| @ -64,7 +65,12 @@ def event_trigger_handler(event_uuid: str, trigger_name: str): | |||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @CELERY_APP.task(bind=True, base=MonitoredTask) | @CELERY_APP.task( | ||||||
|  |     bind=True, | ||||||
|  |     autoretry_for=(NotificationTransportError), | ||||||
|  |     retry_backoff=True, | ||||||
|  |     base=MonitoredTask, | ||||||
|  | ) | ||||||
| def notification_transport( | def notification_transport( | ||||||
|     self: MonitoredTask, notification_pk: int, transport_pk: int |     self: MonitoredTask, notification_pk: int, transport_pk: int | ||||||
| ): | ): | ||||||
| @ -77,6 +83,6 @@ def notification_transport( | |||||||
|         ) |         ) | ||||||
|         transport.send(notification) |         transport.send(notification) | ||||||
|         self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL)) |         self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL)) | ||||||
|     except Exception as exc: |     except NotificationTransportError as exc: | ||||||
|         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) |         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) | ||||||
|         raise exc |         raise exc | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer