From 8192d081070e1723f3ec0c806d49f5b322ecd80a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 13 Jun 2024 17:47:39 +0200 Subject: [PATCH 1/5] add a webhook for verify a presentation --- ...05_alter_file_datas_created_at_and_more.py | 84 ++++++++++++++++ ...lter_user_is_active_alter_user_is_admin.py | 22 +++++ trustchain_idhub/settings.py | 3 +- trustchain_idhub/urls.py | 1 + webhook/__init__.py | 0 webhook/admin.py | 3 + webhook/apps.py | 6 ++ webhook/forms.py | 1 + webhook/migrations/0001_initial.py | 27 ++++++ webhook/migrations/__init__.py | 0 webhook/models.py | 7 ++ webhook/tables.py | 67 +++++++++++++ webhook/templates/token.html | 14 +++ webhook/tests.py | 3 + webhook/urls.py | 13 +++ webhook/views.py | 96 +++++++++++++++++++ 16 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 idhub/migrations/0005_alter_file_datas_created_at_and_more.py create mode 100644 idhub_auth/migrations/0002_alter_user_is_active_alter_user_is_admin.py create mode 100644 webhook/__init__.py create mode 100644 webhook/admin.py create mode 100644 webhook/apps.py create mode 100644 webhook/forms.py create mode 100644 webhook/migrations/0001_initial.py create mode 100644 webhook/migrations/__init__.py create mode 100644 webhook/models.py create mode 100644 webhook/tables.py create mode 100644 webhook/templates/token.html create mode 100644 webhook/tests.py create mode 100644 webhook/urls.py create mode 100644 webhook/views.py diff --git a/idhub/migrations/0005_alter_file_datas_created_at_and_more.py b/idhub/migrations/0005_alter_file_datas_created_at_and_more.py new file mode 100644 index 0000000..42ba9f8 --- /dev/null +++ b/idhub/migrations/0005_alter_file_datas_created_at_and_more.py @@ -0,0 +1,84 @@ +# Generated by Django 4.2.5 on 2024-06-13 08:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('idhub', '0004_alter_event_type'), + ] + + operations = [ + migrations.AlterField( + model_name='file_datas', + name='created_at', + field=models.DateTimeField(auto_now=True, verbose_name='Date'), + ), + migrations.AlterField( + model_name='file_datas', + name='file_name', + field=models.CharField(max_length=250, verbose_name='File'), + ), + migrations.AlterField( + model_name='file_datas', + name='success', + field=models.BooleanField(default=True, verbose_name='Success'), + ), + migrations.AlterField( + model_name='schemas', + name='_description', + field=models.CharField( + db_column='description', + max_length=250, + null=True, + verbose_name='Description', + ), + ), + migrations.AlterField( + model_name='schemas', + name='_name', + field=models.TextField(db_column='name', null=True, verbose_name='Name'), + ), + migrations.AlterField( + model_name='schemas', + name='created_at', + field=models.DateTimeField(auto_now=True, verbose_name='Date'), + ), + migrations.AlterField( + model_name='schemas', + name='file_schema', + field=models.CharField(max_length=250, verbose_name='Schema'), + ), + migrations.AlterField( + model_name='verificablecredential', + name='issued_on', + field=models.DateTimeField(null=True, verbose_name='Issued on'), + ), + migrations.AlterField( + model_name='verificablecredential', + name='status', + field=models.PositiveSmallIntegerField( + choices=[(1, 'Enabled'), (2, 'Issued'), (3, 'Revoked'), (4, 'Expired')], + default=1, + verbose_name='Status', + ), + ), + migrations.AlterField( + model_name='verificablecredential', + name='type', + field=models.CharField(max_length=250, verbose_name='Type'), + ), + migrations.AlterField( + model_name='verificablecredential', + name='user', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='vcredentials', + to=settings.AUTH_USER_MODEL, + verbose_name='User', + ), + ), + ] diff --git a/idhub_auth/migrations/0002_alter_user_is_active_alter_user_is_admin.py b/idhub_auth/migrations/0002_alter_user_is_active_alter_user_is_admin.py new file mode 100644 index 0000000..3d50023 --- /dev/null +++ b/idhub_auth/migrations/0002_alter_user_is_active_alter_user_is_admin.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.5 on 2024-06-13 08:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('idhub_auth', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='is_active', + field=models.BooleanField(default=True, verbose_name='is active'), + ), + migrations.AlterField( + model_name='user', + name='is_admin', + field=models.BooleanField(default=False, verbose_name='is admin'), + ), + ] diff --git a/trustchain_idhub/settings.py b/trustchain_idhub/settings.py index 18b9a4d..473fa24 100644 --- a/trustchain_idhub/settings.py +++ b/trustchain_idhub/settings.py @@ -82,7 +82,8 @@ INSTALLED_APPS = [ 'idhub_auth', 'oidc4vp', 'idhub', - 'promotion' + 'promotion', + 'webhook' ] MIDDLEWARE = [ diff --git a/trustchain_idhub/urls.py b/trustchain_idhub/urls.py index e8468a3..a4bc0b1 100644 --- a/trustchain_idhub/urls.py +++ b/trustchain_idhub/urls.py @@ -26,4 +26,5 @@ urlpatterns = [ path('', include('idhub.urls')), path('oidc4vp/', include('oidc4vp.urls')), path('promotion/', include('promotion.urls')), + path('webhook/', include('webhook.urls')), ] diff --git a/webhook/__init__.py b/webhook/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/webhook/admin.py b/webhook/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/webhook/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/webhook/apps.py b/webhook/apps.py new file mode 100644 index 0000000..60ee235 --- /dev/null +++ b/webhook/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class WebhookConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'webhook' diff --git a/webhook/forms.py b/webhook/forms.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/webhook/forms.py @@ -0,0 +1 @@ + diff --git a/webhook/migrations/0001_initial.py b/webhook/migrations/0001_initial.py new file mode 100644 index 0000000..7bad033 --- /dev/null +++ b/webhook/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.5 on 2024-06-13 08:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='Token', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ('token', models.UUIDField()), + ], + ), + ] diff --git a/webhook/migrations/__init__.py b/webhook/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/webhook/models.py b/webhook/models.py new file mode 100644 index 0000000..54218c2 --- /dev/null +++ b/webhook/models.py @@ -0,0 +1,7 @@ +from django.db import models + +# Create your models here. + + +class Token(models.Model): + token = models.UUIDField() diff --git a/webhook/tables.py b/webhook/tables.py new file mode 100644 index 0000000..498a38e --- /dev/null +++ b/webhook/tables.py @@ -0,0 +1,67 @@ +import django_tables2 as tables +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ + +from webhook.models import Token + + +class ButtonColumn(tables.Column): + attrs = { + "a": { + "type": "button", + "class": "text-danger", + "title": "Remove", + } + } + # it makes no sense to order a column of buttons + orderable = False + # django_tables will only call the render function if it doesn't find + # any empty values in the data, so we stop it from matching the data + # to any value considered empty + empty_values = () + + def render(self): + return format_html('') + + +class TokensTable(tables.Table): + delete = ButtonColumn( + verbose_name=_("Delete"), + linkify={ + "viewname": "webhook:delete_token", + "args": [tables.A("pk")] + }, + orderable=False + ) + + token = tables.Column(verbose_name=_("Token"), empty_values=()) + + def render_view_user(self): + return format_html('') + + # def render_token(self, record): + # return record.get_memberships() + + # def order_membership(self, queryset, is_descending): + # # TODO: Test that this doesn't return more rows than it should + # queryset = queryset.order_by( + # ("-" if is_descending else "") + "memberships__type" + # ) + + # return (queryset, True) + + # def render_role(self, record): + # return record.get_roles() + + # def order_role(self, queryset, is_descending): + # queryset = queryset.order_by( + # ("-" if is_descending else "") + "roles" + # ) + + # return (queryset, True) + + class Meta: + model = Token + template_name = "idhub/custom_table.html" + fields = ("token", "view_user") + diff --git a/webhook/templates/token.html b/webhook/templates/token.html new file mode 100644 index 0000000..7ab829b --- /dev/null +++ b/webhook/templates/token.html @@ -0,0 +1,14 @@ +{% extends "idhub/base_admin.html" %} +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block content %} +

+ + {{ subtitle }} +

+{% render_table table %} +
+ {% translate "Generate a new token" %} +
+{% endblock %} diff --git a/webhook/tests.py b/webhook/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/webhook/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/webhook/urls.py b/webhook/urls.py new file mode 100644 index 0000000..c0c863a --- /dev/null +++ b/webhook/urls.py @@ -0,0 +1,13 @@ +from webhook import views + +from django.urls import path + + +app_name = 'webhook' + +urlpatterns = [ + path('verify/', views.webhook_verify, name='verify'), + path('tokens/', views.WebHookTokenView.as_view(), name='tokens'), + path('tokens/new', views.TokenNewView.as_view(), name='new_token'), + path('tokens//del', views.TokenDeleteView.as_view(), name='delete_token'), +] diff --git a/webhook/views.py b/webhook/views.py new file mode 100644 index 0000000..59245e5 --- /dev/null +++ b/webhook/views.py @@ -0,0 +1,96 @@ +import json + +from django.shortcuts import get_object_or_404, redirect +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.csrf import csrf_exempt +from django.views.generic.edit import DeleteView +from django.views.generic.base import View +from django.http import JsonResponse +from django_tables2 import SingleTableView +from pyvckit.verify import verify_vp, verify_vc +from uuid import uuid4 + +from idhub.mixins import AdminView +from webhook.models import Token +from webhook.tables import TokensTable + + +@csrf_exempt +def webhook_verify(request): + if request.method == 'POST': + auth_header = request.headers.get('Authorization') + if not auth_header or not auth_header.startswith('Bearer '): + return JsonResponse({'error': 'Invalid or missing token'}, status=401) + + token = auth_header.split(' ')[1] + tk = Token.objects.filter(token=token).first() + if not tk: + return JsonResponse({'error': 'Invalid or missing token'}, status=401) + + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({'error': 'Invalid JSON'}, status=400) + + typ = data.get("type") + vc = data.get("data") + try: + vc = json.dumps(vc) + except Exception: + return JsonResponse({'error': 'Invalid JSON'}, status=400) + + func = verify_vp + if typ == "credential": + func = verify_vc + + if func(vc): + return JsonResponse({'status': 'success'}, status=200) + + return JsonResponse({'status': 'fail'}, status=200) + + return JsonResponse({'error': 'Invalid request method'}, status=400) + + +class WebHookTokenView(AdminView, SingleTableView): + template_name = "token.html" + title = _('Token') + section = "" + subtitle = _('Managament Tokens') + icon = 'bi bi-key' + model = Token + table_class = TokensTable + + def get_queryset(self): + """ + Override the get_queryset method to filter events based on the user type. + """ + return Token.objects.filter().order_by("-id") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'tokens': Token.objects, + }) + return context + + +class TokenDeleteView(AdminView, DeleteView): + model = Token + + def get(self, request, *args, **kwargs): + self.check_valid_user() + self.pk = kwargs['pk'] + self.object = get_object_or_404(self.model, pk=self.pk) + self.object.delete() + + return redirect('webhook:tokens') + + +class TokenNewView(AdminView, View): + + def get(self, request, *args, **kwargs): + self.check_valid_user() + Token.objects.create(token=uuid4()) + + return redirect('webhook:tokens') + From b7233a7ca856288befa9dbd8d927594548a7c7f9 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 14 Jun 2024 08:45:45 +0200 Subject: [PATCH 2/5] add link to webhook in base --- idhub/templates/idhub/base_admin.html | 5 +++++ webhook/views.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/idhub/templates/idhub/base_admin.html b/idhub/templates/idhub/base_admin.html index db8354e..79066ed 100644 --- a/idhub/templates/idhub/base_admin.html +++ b/idhub/templates/idhub/base_admin.html @@ -155,6 +155,11 @@ +