From f04f4eaa3fb29e78998c4fc741390ed66b5eb305 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 17 Jun 2022 15:34:59 +0300 Subject: [PATCH] Update public endpoint for outgoing webhooks - Add abilities to create, update and delete outgoing webhooks by public api endpoint --- engine/apps/public_api/serializers/action.py | 75 +++++++++++++++++++- engine/apps/public_api/views/action.py | 37 ++++++++-- 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/engine/apps/public_api/serializers/action.py b/engine/apps/public_api/serializers/action.py index db202b22..963aacbc 100644 --- a/engine/apps/public_api/serializers/action.py +++ b/engine/apps/public_api/serializers/action.py @@ -1,17 +1,88 @@ +import json + +from django.core.validators import URLValidator, ValidationError +from jinja2 import Template, TemplateError from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator from apps.alerts.models import CustomButton from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField +from common.api_helpers.utils import CurrentOrganizationDefault -class ActionSerializer(serializers.ModelSerializer): +class ActionCreateSerializer(serializers.ModelSerializer): id = serializers.CharField(read_only=True, source="public_primary_key") - team_id = TeamPrimaryKeyRelatedField(allow_null=True, source="team") + organization = serializers.HiddenField(default=CurrentOrganizationDefault()) + team_id = TeamPrimaryKeyRelatedField(required=False, allow_null=True, source="team") class Meta: model = CustomButton fields = [ "id", "name", + "organization", "team_id", + "webhook", + "data", + "user", + "password", + "authorization_header", + "forward_whole_payload", ] + extra_kwargs = { + "name": {"required": True, "allow_null": False, "allow_blank": False}, + "webhook": {"required": True, "allow_null": False, "allow_blank": False}, + "data": {"required": False, "allow_null": True, "allow_blank": False}, + "user": {"required": False, "allow_null": True, "allow_blank": False}, + "password": {"required": False, "allow_null": True, "allow_blank": False}, + "authorization_header": {"required": False, "allow_null": True, "allow_blank": False}, + "forward_whole_payload": {"required": False, "allow_null": True}, + } + + validators = [UniqueTogetherValidator(queryset=CustomButton.objects.all(), fields=["name", "organization"])] + + def validate_webhook(self, webhook): + if webhook: + try: + URLValidator()(webhook) + except ValidationError: + raise serializers.ValidationError("Webhook is incorrect") + return webhook + return None + + def validate_data(self, data): + if not data: + return None + + try: + json.loads(data) + except ValueError: + raise serializers.ValidationError("Data has incorrect format") + + try: + Template(data) + except TemplateError: + raise serializers.ValidationError("Data has incorrect template") + + return data + + def validate_forward_whole_payload(self, data): + if data is None: + return False + return data + + +class ActionUpdateSerializer(ActionCreateSerializer): + team_id = TeamPrimaryKeyRelatedField(source="team", read_only=True) + + class Meta(ActionCreateSerializer.Meta): + + extra_kwargs = { + "name": {"required": False, "allow_null": False, "allow_blank": False}, + "webhook": {"required": False, "allow_null": False, "allow_blank": False}, + "data": {"required": False, "allow_null": True, "allow_blank": False}, + "user": {"required": False, "allow_null": True, "allow_blank": False}, + "password": {"required": False, "allow_null": True, "allow_blank": False}, + "authorization_header": {"required": False, "allow_null": True, "allow_blank": False}, + "forward_whole_payload": {"required": False, "allow_null": True}, + } diff --git a/engine/apps/public_api/views/action.py b/engine/apps/public_api/views/action.py index 60ca1465..0e5944eb 100644 --- a/engine/apps/public_api/views/action.py +++ b/engine/apps/public_api/views/action.py @@ -1,25 +1,26 @@ from django_filters import rest_framework as filters -from rest_framework import mixins from rest_framework.permissions import IsAuthenticated -from rest_framework.viewsets import GenericViewSet +from rest_framework.viewsets import ModelViewSet from apps.alerts.models import CustomButton from apps.auth_token.auth import ApiTokenAuthentication -from apps.public_api.serializers.action import ActionSerializer +from apps.public_api.serializers.action import ActionCreateSerializer, ActionUpdateSerializer from apps.public_api.throttlers.user_throttle import UserThrottle +from apps.user_management.organization_log_creator import OrganizationLogType, create_organization_log from common.api_helpers.filters import ByTeamFilter -from common.api_helpers.mixins import RateLimitHeadersMixin +from common.api_helpers.mixins import PublicPrimaryKeyMixin, RateLimitHeadersMixin, UpdateSerializerMixin from common.api_helpers.paginators import FiftyPageSizePaginator -class ActionView(RateLimitHeadersMixin, mixins.ListModelMixin, GenericViewSet): +class ActionView(RateLimitHeadersMixin, PublicPrimaryKeyMixin, UpdateSerializerMixin, ModelViewSet): authentication_classes = (ApiTokenAuthentication,) permission_classes = (IsAuthenticated,) pagination_class = FiftyPageSizePaginator throttle_classes = [UserThrottle] model = CustomButton - serializer_class = ActionSerializer + serializer_class = ActionCreateSerializer + update_serializer_class = ActionUpdateSerializer filter_backends = (filters.DjangoFilterBackend,) filterset_class = ByTeamFilter @@ -32,3 +33,27 @@ class ActionView(RateLimitHeadersMixin, mixins.ListModelMixin, GenericViewSet): queryset = queryset.filter(name=action_name) return queryset + + def perform_create(self, serializer): + serializer.save() + instance = serializer.instance + organization = self.request.auth.organization + user = self.request.user + description = f"Custom action {instance.name} was created" + create_organization_log(organization, user, OrganizationLogType.TYPE_CUSTOM_ACTION_CREATED, description) + + def perform_update(self, serializer): + organization = self.request.auth.organization + user = self.request.user + old_state = serializer.instance.repr_settings_for_client_side_logging + serializer.save() + new_state = serializer.instance.repr_settings_for_client_side_logging + description = f"Custom action {serializer.instance.name} was changed " f"from:\n{old_state}\nto:\n{new_state}" + create_organization_log(organization, user, OrganizationLogType.TYPE_CUSTOM_ACTION_CHANGED, description) + + def perform_destroy(self, instance): + organization = self.request.auth.organization + user = self.request.user + description = f"Custom action {instance.name} was deleted" + create_organization_log(organization, user, OrganizationLogType.TYPE_CUSTOM_ACTION_DELETED, description) + instance.delete()