diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..66656fd --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/api/forms.py b/api/forms.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/api/forms.py @@ -0,0 +1 @@ + diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000..22c5a1f --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 5.0.6 on 2024-09-19 15:09 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Token", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("token", models.UUIDField()), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..b8bbc24 --- /dev/null +++ b/api/models.py @@ -0,0 +1,9 @@ +from django.db import models +from user.models import User + +# Create your models here. + + +class Token(models.Model): + token = models.UUIDField() + owner = models.ForeignKey(User, on_delete=models.CASCADE) diff --git a/api/tables.py b/api/tables.py new file mode 100644 index 0000000..ac1cc7a --- /dev/null +++ b/api/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 api.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": "api: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 = "custom_table.html" + fields = ("token", "view_user") + diff --git a/api/templates/custom_table.html b/api/templates/custom_table.html new file mode 100644 index 0000000..496ddec --- /dev/null +++ b/api/templates/custom_table.html @@ -0,0 +1,100 @@ +{% load django_tables2 %} +{% load i18n %} +{% block table-wrapper %} +
+ {% block table %} + + {% block table.thead %} + {% if table.show_header %} + + + {% for column in table.columns %} + + {% endfor %} + + + {% endif %} + {% endblock table.thead %} + {% block table.tbody %} + + {% for row in table.paginated_rows %} + {% block table.tbody.row %} + + {% for column, cell in row.items %} + + {% endfor %} + + {% endblock table.tbody.row %} + {% empty %} + {% if table.empty_text %} + {% block table.tbody.empty_text %} + + {% endblock table.tbody.empty_text %} + {% endif %} + {% endfor %} + + {% endblock table.tbody %} + {% block table.tfoot %} + {% if table.has_footer %} + + + {% for column in table.columns %} + + {% endfor %} + + + {% endif %} + {% endblock table.tfoot %} +
+ {% if column.orderable %} + {{ column.header }} + {% else %} + {{ column.header }} + {% endif %} +
{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}
{{ table.empty_text }}
{{ column.footer }}
+ {% endblock table %} + + {% block pagination %} + {% if table.page and table.paginator.num_pages > 1 %} + + {% endif %} + {% endblock pagination %} +
+{% endblock table-wrapper %} diff --git a/api/templates/token.html b/api/templates/token.html new file mode 100644 index 0000000..5185090 --- /dev/null +++ b/api/templates/token.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block content %} +

+ + {{ subtitle }} +

+{% render_table table %} +
+ {% translate "Generate a new token" %} +
+{% endblock %} diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..f3c8028 --- /dev/null +++ b/api/urls.py @@ -0,0 +1,13 @@ +from api import views + +from django.urls import path + + +app_name = 'api' + +urlpatterns = [ + path('snapshot/', views.NewSnapshot, name='new_snapshot'), + path('tokens/', views.TokenView.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/api/views.py b/api/views.py new file mode 100644 index 0000000..5a119f9 --- /dev/null +++ b/api/views.py @@ -0,0 +1,116 @@ +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.core.exceptions import ValidationError +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 uuid import uuid4 + +from dashboard.mixins import DashboardView +from evidence.models import Annotation +from evidence.parse import Build +from user.models import User +from api.models import Token +from api.tables import TokensTable + + +def save_in_disk(data, user): + pass + + +@csrf_exempt +def NewSnapshot(request): + # Accept only posts + if request.method != 'POST': + return JsonResponse({'error': 'Invalid request method'}, status=400) + + # Authentication + # 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) + + # Validation snapshot + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({'error': 'Invalid JSON'}, status=400) + + # try: + # Build(data, None, check=True) + # except Exception: + # return JsonResponse({'error': 'Invalid Snapshot'}, status=400) + + exist_annotation = Annotation.objects.filter( + uuid=data['uuid'] + ).first() + + if exist_annotation: + raise ValidationError("error: the snapshot {} exist".format(data['uuid'])) + + # Process snapshot + # save_in_disk(data, tk.user) + + try: + # Build(data, tk.user) + user = User.objects.get(email="user@example.org") + Build(data, user) + except Exception: + return JsonResponse({'status': 'fail'}, status=200) + + return JsonResponse({'status': 'success'}, status=200) + + + + +class TokenView(DashboardView, SingleTableView): + template_name = "token.html" + title = _("Credential management") + section = "Credential" + 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(DashboardView, 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('api:tokens') + + +class TokenNewView(DashboardView, View): + + def get(self, request, *args, **kwargs): + # self.check_valid_user() + Token.objects.create(token=uuid4()) + + return redirect('api:tokens') +