Files
authentik/authentik/events/api/notification_transports.py
Jens L. ce23209ae8 events: add configurable headers to webhooks (#13602)
* events: add configurable headers to webhooks

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make it a full thing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix migration

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-03-21 19:37:15 +00:00

107 lines
3.7 KiB
Python

"""NotificationTransport API Views"""
from typing import Any
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.events.models import (
Event,
Notification,
NotificationSeverity,
NotificationTransport,
NotificationTransportError,
TransportMode,
)
from authentik.events.utils import get_user
from authentik.rbac.decorators import permission_required
class NotificationTransportSerializer(ModelSerializer):
"""NotificationTransport Serializer"""
mode_verbose = SerializerMethodField()
def get_mode_verbose(self, instance: NotificationTransport) -> str:
"""Return selected mode with a UI Label"""
return TransportMode(instance.mode).label
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
"""Ensure the required fields are set."""
mode = attrs.get("mode")
if mode in [TransportMode.WEBHOOK, TransportMode.WEBHOOK_SLACK]:
if "webhook_url" not in attrs or attrs.get("webhook_url", "") == "":
raise ValidationError({"webhook_url": "Webhook URL may not be empty."})
return attrs
class Meta:
model = NotificationTransport
fields = [
"pk",
"name",
"mode",
"mode_verbose",
"webhook_url",
"webhook_mapping_body",
"webhook_mapping_headers",
"send_once",
]
class NotificationTransportTestSerializer(PassiveSerializer):
"""Notification test serializer"""
messages = ListField(child=CharField())
class NotificationTransportViewSet(UsedByMixin, ModelViewSet):
"""NotificationTransport Viewset"""
queryset = NotificationTransport.objects.all()
serializer_class = NotificationTransportSerializer
filterset_fields = ["name", "mode", "webhook_url", "send_once"]
search_fields = ["name", "mode", "webhook_url"]
ordering = ["name"]
@permission_required("authentik_events.change_notificationtransport")
@extend_schema(
responses={
200: NotificationTransportTestSerializer(many=False),
500: OpenApiResponse(description="Failed to test transport"),
},
request=OpenApiTypes.NONE,
)
@action(detail=True, pagination_class=None, filter_backends=[], methods=["post"])
def test(self, request: Request, pk=None) -> Response:
"""Send example notification using selected transport. Requires
Modify permissions."""
transport: NotificationTransport = self.get_object()
event = Event.new(
action="notification_test",
user=get_user(request.user),
context={"foo": "bar"},
)
event.save()
notification = Notification(
severity=NotificationSeverity.NOTICE,
body=f"Test Notification from transport {transport.name}",
user=request.user,
event=event,
)
try:
response = NotificationTransportTestSerializer(
data={"messages": transport.send(notification)}
)
response.is_valid()
return Response(response.data)
except NotificationTransportError as exc:
return Response(str(exc.__cause__ or None), status=500)