diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 224788ef1..f628c0358 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -66,7 +66,6 @@ class IssueSerializer(BaseSerializer): project_id=self.context.get("project_id"), is_active=True, member_id__in=data["assignees"], - is_active=True, ).values_list("member_id", flat=True) # Validate labels are from project diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index 6cd8b2356..830072858 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -36,28 +36,31 @@ class TimezoneMixin: else: timezone.deactivate() + class WebhookMixin: webhook_event = None def finalize_response(self, request, response, *args, **kwargs): response = super().finalize_response(request, response, *args, **kwargs) - if ( self.webhook_event - and self.request.method in ["POST", "PATCH", "DELETE"] + and self.request.method in ["POST", "PATCH"] and response.status_code in [200, 201, 204] ): + # Get the id + object_id = ( + response.data.get("id") if isinstance(response.data, dict) else None + ) + send_webhook.delay( event=self.webhook_event, - event_data=json.dumps(response.data, cls=DjangoJSONEncoder), + event_id=object_id, action=self.request.method, slug=self.workspace_slug, ) - return response - class BaseAPIView(TimezoneMixin, APIView, BasePaginator): authentication_classes = [ APIKeyAuthentication, @@ -139,13 +142,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator): response = super().finalize_response(request, response, *args, **kwargs) # Add custom headers if they exist in the request META - ratelimit_remaining = request.META.get('X-RateLimit-Remaining') + ratelimit_remaining = request.META.get("X-RateLimit-Remaining") if ratelimit_remaining is not None: - response['X-RateLimit-Remaining'] = ratelimit_remaining + response["X-RateLimit-Remaining"] = ratelimit_remaining - ratelimit_reset = request.META.get('X-RateLimit-Reset') + ratelimit_reset = request.META.get("X-RateLimit-Reset") if ratelimit_reset is not None: - response['X-RateLimit-Reset'] = ratelimit_reset + response["X-RateLimit-Reset"] = ratelimit_reset return response @@ -169,4 +172,4 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator): expand = [ expand for expand in self.request.GET.get("expand", "").split(",") if expand ] - return expand if expand else None \ No newline at end of file + return expand if expand else None diff --git a/apiserver/plane/bgtasks/webhook_task.py b/apiserver/plane/bgtasks/webhook_task.py index 57f94dc03..606b9646c 100644 --- a/apiserver/plane/bgtasks/webhook_task.py +++ b/apiserver/plane/bgtasks/webhook_task.py @@ -2,15 +2,63 @@ import requests import uuid import hashlib import json +import hmac # Django imports from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder # Third party imports from celery import shared_task from sentry_sdk import capture_exception -from plane.db.models import Webhook, WebhookLog +from plane.db.models import ( + Webhook, + WebhookLog, + Project, + Issue, + Cycle, + Module, + ModuleIssue, + CycleIssue, + IssueComment, +) +from plane.api.serializers import ( + ProjectSerializer, + IssueSerializer, + CycleSerializer, + ModuleSerializer, + CycleIssueSerializer, + ModuleIssueSerializer, + IssueCommentSerializer, +) + +SERIALIZER_MAPPER = { + "project": ProjectSerializer, + "issue": IssueSerializer, + "cycle": CycleSerializer, + "module": ModuleSerializer, + "cycle_issue": CycleIssueSerializer, + "module_issue": ModuleIssueSerializer, + "issue_comment": IssueCommentSerializer, +} + +MODEL_MAPPER = { + "project": Project, + "issue": Issue, + "cycle": Cycle, + "module": Module, + "cycle_issue": CycleIssue, + "module_issue": ModuleIssue, + "issue_comment": IssueComment, +} + + +def get_model_data(event, event_id): + model = MODEL_MAPPER.get(event) + queryset = model.objects.get(pk=event_id) + serializer = SERIALIZER_MAPPER.get(event) + return serializer(queryset).data @shared_task( @@ -20,7 +68,7 @@ from plane.db.models import Webhook, WebhookLog max_retries=5, retry_jitter=True, ) -def webhook_task(self, webhook, slug, event, event_data, action): +def webhook_task(self, webhook, slug, event, event_id, action): try: webhook = Webhook.objects.get(id=webhook, workspace__slug=slug) @@ -31,19 +79,26 @@ def webhook_task(self, webhook, slug, event, event_data, action): "X-Plane-Event": event, } - # Your secret key + event_data = get_model_data(event=event, event_id=event_id) + + # # Your secret key + event_data = ( + json.loads(json.dumps(event_data, cls=DjangoJSONEncoder)) + if event_data is not None + else None + ) + + # Use HMAC for generating signature if webhook.secret_key: - # Concatenate the data and the secret key - message = event_data + webhook.secret_key - - # Create a SHA-256 hash of the message - sha256 = hashlib.sha256() - sha256.update(message.encode("utf-8")) - signature = sha256.hexdigest() + event_data_json = json.dumps(event_data) if event_data is not None else '{}' + hmac_signature = hmac.new( + webhook.secret_key.encode("utf-8"), + event_data_json.encode("utf-8"), + hashlib.sha256 + ) + signature = hmac_signature.hexdigest() headers["X-Plane-Signature"] = signature - event_data = json.loads(event_data) if event_data is not None else None - action = { "POST": "create", "PATCH": "update", @@ -103,6 +158,7 @@ def webhook_task(self, webhook, slug, event, event_data, action): raise requests.RequestException() except Exception as e: + print(e) if settings.DEBUG: print(e) capture_exception(e) @@ -110,7 +166,7 @@ def webhook_task(self, webhook, slug, event, event_data, action): @shared_task() -def send_webhook(event, event_data, action, slug): +def send_webhook(event, event_id, action, slug): try: webhooks = Webhook.objects.filter(workspace__slug=slug, is_active=True) @@ -130,7 +186,7 @@ def send_webhook(event, event_data, action, slug): webhooks = webhooks.filter(issue_comment=True) for webhook in webhooks: - webhook_task.delay(webhook.id, slug, event, event_data, action) + webhook_task.delay(webhook.id, slug, event, event_id, action) except Exception as e: if settings.DEBUG: