2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-05 06:48:48 +00:00

initial webhook view #2036

This commit is contained in:
Matthias 2021-09-12 16:14:06 +02:00
parent 71ca60d679
commit f600083dee
No known key found for this signature in database
GPG Key ID: F50EF5741D33E076
5 changed files with 205 additions and 2 deletions

View File

@ -54,7 +54,6 @@ admin.site.site_header = "InvenTree Admin"
apipatterns = [ apipatterns = [
url(r'^barcode/', include(barcode_api_urls)), url(r'^barcode/', include(barcode_api_urls)),
url(r'^common/', include(common_api_urls)),
url(r'^part/', include(part_api_urls)), url(r'^part/', include(part_api_urls)),
url(r'^bom/', include(bom_api_urls)), url(r'^bom/', include(bom_api_urls)),
url(r'^company/', include(company_api_urls)), url(r'^company/', include(company_api_urls)),
@ -70,6 +69,9 @@ apipatterns = [
# Plugin endpoints # Plugin endpoints
url(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'), url(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'),
# Webhook enpoint
path('', include(common_api_urls)),
# InvenTree information endpoint # InvenTree information endpoint
url(r'^$', InfoView.as_view(), name='api-inventree-info'), url(r'^$', InfoView.as_view(), name='api-inventree-info'),

View File

@ -5,7 +5,7 @@ from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin
from .models import InvenTreeSetting, InvenTreeUserSetting from .models import InvenTreeSetting, InvenTreeUserSetting, WebhookEndpoint
class SettingsAdmin(ImportExportModelAdmin): class SettingsAdmin(ImportExportModelAdmin):
@ -18,5 +18,11 @@ class UserSettingsAdmin(ImportExportModelAdmin):
list_display = ('key', 'value', 'user', ) list_display = ('key', 'value', 'user', )
class WebhookAdmin(ImportExportModelAdmin):
list_display = ('endpoint_id', 'name', 'active', 'user')
admin.site.register(InvenTreeSetting, SettingsAdmin) admin.site.register(InvenTreeSetting, SettingsAdmin)
admin.site.register(InvenTreeUserSetting, UserSettingsAdmin) admin.site.register(InvenTreeUserSetting, UserSettingsAdmin)
admin.site.register(WebhookEndpoint, WebhookAdmin)

View File

@ -5,5 +5,122 @@ Provides a JSON API for common components.
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import json
from secrets import compare_digest
from django.utils.decorators import method_decorator
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import PermissionDenied, NotFound, NotAcceptable
from .models import WebhookEndpoint
class CsrfExemptMixin(object):
"""
Exempts the view from CSRF requirements.
"""
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(CsrfExemptMixin, self).dispatch(*args, **kwargs)
class VerificationMethod:
NONE = 0
TOKEN = 1
HMAC = 2
class WebhookView(CsrfExemptMixin, APIView):
"""
Endpoint for receiving webhoks.
"""
authentication_classes = []
permission_classes = []
model_class = WebhookEndpoint
# Token
TOKEN_NAME = "Token"
VERIFICATION_METHOD = VerificationMethod.NONE
MESSAGE_OK = "Message was received."
MESSAGE_TOKEN_ERROR = "Incorrect token in header."
def post(self, request, endpoint, *args, **kwargs):
self.init(request, *args, **kwargs)
# get webhook definition
self.get_webhook(endpoint, *args, **kwargs)
# check headers
headers = request.headers
self.validate_token(headers)
# process data
try:
payload = json.loads(request.body)
except json.decoder.JSONDecodeError as error:
raise NotAcceptable(error.msg)
self.save_data(payload, headers, request)
self.process_payload(payload, headers, request)
# return results
return_kwargs = self.get_result(payload, headers, request)
return Response(**return_kwargs)
# To be overridden
def init(self, request, *args, **kwargs):
self.token = ''
self.verify = self.VERIFICATION_METHOD
def get_webhook(self, endpoint):
try:
webhook = self.model_class.objects.get(endpoint_id=endpoint)
self.webhook = webhook
return self.process_webhook()
except self.model_class.DoesNotExist:
raise NotFound()
def process_webhook(self):
if self.webhook.token:
self.token = self.webhook.token
self.verify = VerificationMethod.TOKEN
return True
def validate_token(self, headers):
token = headers.get(self.TOKEN_NAME, "")
# no token
if self.verify == VerificationMethod.NONE:
return True
# static token
elif self.verify == VerificationMethod.TOKEN:
if not compare_digest(token, self.token):
raise PermissionDenied(self.MESSAGE_TOKEN_ERROR)
return True
# hmac token
elif self.verify == VerificationMethod.HMAC:
# TODO write check
return True
def save_data(self, payload, headers=None, request=None):
# TODO safe data
return
def process_payload(self, payload, headers=None, request=None):
return
def get_result(self, payload, headers=None, request=None):
context = {}
context['data'] = {'message': self.MESSAGE_OK}
context['status'] = 200
return context
common_api_urls = [ common_api_urls = [
path('webhook/<slug:endpoint>/', WebhookView.as_view(), name='api-webhook'),
] ]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.4 on 2021-09-12 12:42
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('common', '0011_auto_20210722_2114'),
]
operations = [
migrations.CreateModel(
name='WebhookEndpoint',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('endpoint_id', models.CharField(default=uuid.uuid4, editable=False, help_text='Endpoint at which this webhook is received', max_length=255, verbose_name='Endpoint')),
('name', models.CharField(blank=True, help_text='Name for this webhook', max_length=255, null=True, verbose_name='Name')),
('active', models.BooleanField(default=True, help_text='Is this webhook active', verbose_name='Active')),
('token', models.CharField(blank=True, default=uuid.uuid4, help_text='Token for access', max_length=255, null=True, verbose_name='Token')),
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
),
]

View File

@ -9,6 +9,7 @@ from __future__ import unicode_literals
import os import os
import decimal import decimal
import math import math
import uuid
from django.db import models, transaction from django.db import models, transaction
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -1165,3 +1166,52 @@ class ColorTheme(models.Model):
return True return True
return False return False
class WebhookEndpoint(models.Model):
""" Defines a Webhook entdpoint
Attributes:
endpoint_id: Path to the webhook,
name: Name of the webhook,
active: Is this webhook active?,
user: User associated with webhook,
token: Token for sending a webhook,
"""
endpoint_id = models.CharField(
max_length=255,
verbose_name=_('Endpoint'),
help_text=_('Endpoint at which this webhook is received'),
default=uuid.uuid4,
editable=False,
)
name = models.CharField(
max_length=255,
blank=True, null=True,
verbose_name=_('Name'),
help_text=_('Name for this webhook')
)
active = models.BooleanField(
default=True,
verbose_name=_('Active'),
help_text=_('Is this webhook active')
)
user = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('User'),
help_text=_('User'),
)
token = models.CharField(
max_length=255,
blank=True, null=True,
verbose_name=_('Token'),
help_text=_('Token for access'),
default=uuid.uuid4,
)