mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-03-18 19:12:07 +00:00
feat: add webhook API
This commit is contained in:
@@ -5,3 +5,4 @@ from .mixin import *
|
||||
from .patch import *
|
||||
from .permission import *
|
||||
from .serializer import *
|
||||
from .webhook import *
|
||||
|
||||
64
apps/common/api/webhook.py
Normal file
64
apps/common/api/webhook.py
Normal 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)
|
||||
@@ -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+)`')
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
from django.dispatch import Signal
|
||||
|
||||
django_ready = Signal()
|
||||
webhook_signal = Signal()
|
||||
|
||||
@@ -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'))
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user