feat: add webhook API

This commit is contained in:
Bai
2026-03-10 17:00:26 +08:00
parent d0a9d6525d
commit 814303633d
7 changed files with 80 additions and 2 deletions

View File

@@ -5,3 +5,4 @@ from .mixin import *
from .patch import *
from .permission import *
from .serializer import *
from .webhook import *

View File

@@ -0,0 +1,64 @@
import hashlib
import hmac
from django.conf import settings
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
from common.signals import webhook_signal
class WebhookApi(APIView):
"""
data:
{
"event": "license_updated",
"payload": {
}
"""
authentication_classes = ()
permission_classes = (AllowAny,)
signature_header = 'HTTP_X_WEBHOOK_SIGNATURE'
@staticmethod
def _normalize_signature(signature):
signature = str(signature or '').strip()
if signature.startswith('sha256='):
return signature.split('=', 1)[1]
return signature
def _is_valid_signature(self, body, signature):
token = getattr(settings, 'WEBHOOK_TOKEN', '')
if not token:
return False
expected = hmac.new(
token.encode('utf-8'),
msg=body,
digestmod=hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, self._normalize_signature(signature))
def post(self, request, *args, **kwargs):
signature = request.META.get(self.signature_header, '')
body = request.body or b''
data = request.data
event = data.get('event', '')
payload = data.get('payload', {})
if not signature:
return Response({'detail': 'Missing X-Webhook-Signature'}, status=status.HTTP_400_BAD_REQUEST)
if not self._is_valid_signature(body, signature):
return Response({'detail': 'Invalid webhook signature'}, status=status.HTTP_403_FORBIDDEN)
webhook_signal.send(
sender=self.__class__,
event=event,
payload=payload,
headers=request.headers,
)
return Response({'detail': 'Webhook accepted'}, status=status.HTTP_202_ACCEPTED)

View File

@@ -12,7 +12,7 @@ from django.dispatch import receiver
from jumpserver.utils import get_current_request
from .local import thread_local
from .signals import django_ready
from .signals import django_ready, webhook_signal
from .utils import get_logger
pattern = re.compile(r'FROM `(\w+)`')

View File

@@ -4,3 +4,4 @@
from django.dispatch import Signal
django_ready = Signal()
webhook_signal = Signal()

View File

@@ -2,6 +2,7 @@
#
from django.urls import path
from django.conf import settings
from .. import api
@@ -11,3 +12,6 @@ urlpatterns = [
path('resources/cache/', api.ResourcesIDCacheApi.as_view(), name='resources-cache'),
path('countries/', api.CountryListApi.as_view(), name='resources-cache'),
]
if settings.WEBHOOK_ENABLED:
urlpatterns.append(path('webhook/', api.WebhookApi.as_view(), name='webhooks'))

View File

@@ -751,6 +751,10 @@ class Config(dict):
'JDMC_ENABLED': False,
'JDMC_SOCK_PATH': '',
'JDMC_LICENSE_PUBLIC_KEY_PATH': '',
# WEBHOOK
'WEBHOOK_ENABLED': False,
'WEBHOOK_TOKEN': '',
}
old_config_map = {

View File

@@ -281,4 +281,8 @@ if Path(VENDOR_TEMPLATES_DIR).is_dir():
JDMC_ENABLED = CONFIG.JDMC_ENABLED
JDMC_SOCK_PATH = CONFIG.JDMC_SOCK_PATH
JDMC_BASE_URL = f"http+unix://{quote(JDMC_SOCK_PATH, safe='')}"
JDMC_LICENSE_PUBLIC_KEY_PATH = CONFIG.JDMC_LICENSE_PUBLIC_KEY_PATH
JDMC_LICENSE_PUBLIC_KEY_PATH = CONFIG.JDMC_LICENSE_PUBLIC_KEY_PATH
# WebHook
WEBHOOK_ENABLED = CONFIG.WEBHOOK_ENABLED
WEBHOOK_TOKEN = CONFIG.WEBHOOK_TOKEN