From b2e8291d6521af87d498e9baabb8d97d4669e5ce Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Fri, 27 Jan 2023 06:54:01 +0100 Subject: [PATCH] frontend: add an HTTP API method to import results --- fifoci/frontend/settings/base.py | 2 + fifoci/frontend/settings/local.py | 2 + fifoci/frontend/settings/production.py | 3 ++ fifoci/frontend/urls.py | 18 +++++---- fifoci/frontend/views.py | 54 +++++++++++++++++++++++++- 5 files changed, 70 insertions(+), 9 deletions(-) diff --git a/fifoci/frontend/settings/base.py b/fifoci/frontend/settings/base.py index 76275ca..33e17ec 100644 --- a/fifoci/frontend/settings/base.py +++ b/fifoci/frontend/settings/base.py @@ -28,6 +28,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) # application restart. SECRET_KEY = os.urandom(32) +IMPORT_API_KEY = os.urandom(32) + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True TEMPLATE_DEBUG = True diff --git a/fifoci/frontend/settings/local.py b/fifoci/frontend/settings/local.py index 9b5ed21..b4f2c44 100644 --- a/fifoci/frontend/settings/local.py +++ b/fifoci/frontend/settings/local.py @@ -1 +1,3 @@ from .base import * + +IMPORT_API_KEY = "TestKey" diff --git a/fifoci/frontend/settings/production.py b/fifoci/frontend/settings/production.py index b9f5a36..b5055c4 100644 --- a/fifoci/frontend/settings/production.py +++ b/fifoci/frontend/settings/production.py @@ -7,6 +7,9 @@ DEBUG = TEMPLATE_DEBUG = False if "SECRET_KEY_FILE" in os.environ: SECRET_KEY = open(os.environ["SECRET_KEY_FILE"]).read() +if "IMPORT_API_KEY_FILE" in os.environ: + IMPORT_API_KEY = open(os.environ["IMPORT_API_KEY_FILE"]).read() + if "STATIC_ROOT" in os.environ: STATIC_ROOT = os.environ["STATIC_ROOT"] diff --git a/fifoci/frontend/urls.py b/fifoci/frontend/urls.py index e676d45..50cde85 100644 --- a/fifoci/frontend/urls.py +++ b/fifoci/frontend/urls.py @@ -10,18 +10,12 @@ from django.urls import include, path, re_path from . import views urlpatterns = [ - # Examples: + # HTML pages for human consumption. re_path(r"^$", views.home, name="home"), - re_path(r"^dff/$", views.dffs_to_test), re_path(r"^dff/(?P[a-zA-Z0-9-]+)/$", views.dff_view, name="dff-view"), re_path( r"^version/(?P[0-9a-f]{40})/$", views.version_view, name="version-view" ), - re_path( - r"^version/(?P[0-9a-f]{40})/json/$", - views.version_view_json, - name="version-view-json", - ), re_path(r"^result/(?P\d+)/$", views.result_view, name="result-view"), re_path( r"^compare/(?P\d+)-(?P\d+)/$", @@ -29,8 +23,16 @@ urlpatterns = [ name="compare-view", ), re_path(r"^about/$", views.about_view, name="about-view"), - re_path(r"^existing-images/$", views.existing_images), re_path(r"^admin/", admin.site.urls), + # API endpoints. + re_path(r"^dff/$", views.dffs_to_test), + re_path( + r"^version/(?P[0-9a-f]{40})/json/$", + views.version_view_json, + name="version-view-json", + ), + re_path(r"^result/import/$", views.import_result, name="import-result"), + re_path(r"^existing-images/$", views.existing_images), ] if settings.DEBUG: diff --git a/fifoci/frontend/views.py b/fifoci/frontend/views.py index 874b8e0..bdf18b1 100644 --- a/fifoci/frontend/views.py +++ b/fifoci/frontend/views.py @@ -4,12 +4,21 @@ from django.conf import settings from django.db.models import Q -from django.http import JsonResponse +from django.http import ( + HttpResponse, + HttpResponseBadRequest, + HttpResponseForbidden, + JsonResponse, +) from django.shortcuts import get_object_or_404, render from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST +from . import importer from .models import FifoTest, Version, Result, Type +import json import os import os.path @@ -176,3 +185,46 @@ def existing_images(request): img = [] hashes = [i[:-4] for i in img if i.endswith(".png")] return JsonResponse(hashes, safe=False) + + +@require_POST +@csrf_exempt +def import_result(request): + if "HTTP_AUTHORIZATION" not in request.META: + return HttpResponseForbidden("Missing 'Authorization' header") + parts = request.META["HTTP_AUTHORIZATION"].split(" ", 1) + if len(parts) < 2: + return HttpResponseForbidden("Invalid 'Authorization' header format") + if parts[0].lower() != "bearer": + return HttpResponseForbidden("Unknown 'Authorization' header type") + if parts[1] != settings.IMPORT_API_KEY: + return HttpResponseForbidden("Wrong bearer token") + + if "meta" not in request.FILES: + return HttpResponseBadRequest("No 'meta' found in uploaded files") + + try: + meta = json.load(request.FILES["meta"]) + except json.JSONDecodeError as e: + return HttpResponseBadRequest(f"Could not parse meta JSON: {e}") + + for key in ("type", "rev", "results"): + if key not in meta: + return HttpResponseBadRequest(f"{key!r} not present in meta JSON") + + type, _ = Type.objects.get_or_create(type=meta["type"]) + ver, parent = importer.get_or_create_ver(meta["rev"]) + + for dff_short_name, result in meta["results"].items(): + try: + dff = FifoTest.objects.get(shortname=dff_short_name) + except FifoTest.DoesNotExist: + continue + + images = {} + for f in request.FILES.getlist("image"): + images[f.name.removesuffix(".png")] = f + + importer.import_result(dff, type, ver, parent, result, images) + + return HttpResponse("OK")