From a57d524273774fed75167f0ec051fc4f2bdad088 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Thu, 18 Mar 2021 14:35:43 +0100 Subject: [PATCH] flows: add API for flow export Signed-off-by: Jens Langhammer --- authentik/admin/urls.py | 5 ----- authentik/admin/views/flows.py | 20 +------------------- authentik/flows/api/flows.py | 23 +++++++++++++++++++++++ swagger.yaml | 20 ++++++++++++++++++++ web/src/pages/flows/FlowListPage.ts | 2 +- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 683604bc6..732cf956e 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -148,11 +148,6 @@ urlpatterns = [ flows.FlowDebugExecuteView.as_view(), name="flow-execute", ), - path( - "flows//export/", - flows.FlowExportView.as_view(), - name="flow-export", - ), # Property Mappings path( "property-mappings/create/", diff --git a/authentik/admin/views/flows.py b/authentik/admin/views/flows.py index b6f172b61..936497fdd 100644 --- a/authentik/admin/views/flows.py +++ b/authentik/admin/views/flows.py @@ -5,7 +5,7 @@ from django.contrib.auth.mixins import ( PermissionRequiredMixin as DjangoPermissionRequiredMixin, ) from django.contrib.messages.views import SuccessMessageMixin -from django.http import HttpRequest, HttpResponse, JsonResponse +from django.http import HttpRequest, HttpResponse from django.urls import reverse_lazy from django.utils.translation import gettext as _ from django.views.generic import DetailView, FormView, UpdateView @@ -15,8 +15,6 @@ from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.forms import FlowForm, FlowImportForm from authentik.flows.models import Flow from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER -from authentik.flows.transfer.common import DataclassEncoder -from authentik.flows.transfer.exporter import FlowExporter from authentik.flows.transfer.importer import FlowImporter from authentik.flows.views import SESSION_KEY_PLAN, FlowPlanner from authentik.lib.utils.urls import redirect_with_qs @@ -108,19 +106,3 @@ class FlowImportView(LoginRequiredMixin, FormView): else: messages.success(self.request, _("Successfully imported flow.")) return super().form_valid(form) - - -class FlowExportView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): - """Export Flow""" - - model = Flow - permission_required = "authentik_flows.export_flow" - - # pylint: disable=unused-argument - def get(self, request: HttpRequest, pk: str) -> HttpResponse: - """Debug exectue flow, setting the current user as pending user""" - flow: Flow = self.get_object() - exporter = FlowExporter(flow) - response = JsonResponse(exporter.export(), encoder=DataclassEncoder, safe=False) - response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"' - return response diff --git a/authentik/flows/api/flows.py b/authentik/flows/api/flows.py index b5485ac8f..2a1406d30 100644 --- a/authentik/flows/api/flows.py +++ b/authentik/flows/api/flows.py @@ -3,10 +3,13 @@ from dataclasses import dataclass from django.core.cache import cache from django.db.models import Model +from django.http.response import JsonResponse from django.shortcuts import get_object_or_404 +from drf_yasg2 import openapi from drf_yasg2.utils import swagger_auto_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action +from rest_framework.exceptions import PermissionDenied from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ( @@ -20,6 +23,8 @@ from rest_framework.viewsets import ModelViewSet from authentik.core.api.utils import CacheSerializer from authentik.flows.models import Flow from authentik.flows.planner import cache_key +from authentik.flows.transfer.common import DataclassEncoder +from authentik.flows.transfer.exporter import FlowExporter class FlowSerializer(ModelSerializer): @@ -87,6 +92,24 @@ class FlowViewSet(ModelViewSet): """Info about cached flows""" return Response(data={"count": len(cache.keys("flow_*"))}) + @swagger_auto_schema( + responses={ + "200": openapi.Response( + "File Attachment", schema=openapi.Schema(type=openapi.TYPE_FILE) + ), + }, + ) + @action(detail=True) + def export(self, request: Request, slug: str) -> Response: + """Export flow to .akflow file""" + flow = self.get_object() + if not request.user.has_perm("authentik_flows.export_flow", flow): + raise PermissionDenied() + exporter = FlowExporter(flow) + response = JsonResponse(exporter.export(), encoder=DataclassEncoder, safe=False) + response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"' + return response + @swagger_auto_schema(responses={200: FlowDiagramSerializer()}) @action(detail=True, methods=["get"]) def diagram(self, request: Request, slug: str) -> Response: diff --git a/swagger.yaml b/swagger.yaml index c6fedb5b5..b9755c193 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -2800,6 +2800,26 @@ paths: type: string format: slug pattern: ^[-a-zA-Z0-9_]+$ + /flows/instances/{slug}/export/: + get: + operationId: flows_instances_export + description: Export flow to .akflow file + parameters: [] + responses: + '200': + description: File Attachment + schema: + type: file + tags: + - flows + parameters: + - name: slug + in: path + description: Visible in the URL. + required: true + type: string + format: slug + pattern: ^[-a-zA-Z0-9_]+$ /outposts/outposts/: get: operationId: outposts_outposts_list diff --git a/web/src/pages/flows/FlowListPage.ts b/web/src/pages/flows/FlowListPage.ts index 17ac5d0ff..88e7b2bcd 100644 --- a/web/src/pages/flows/FlowListPage.ts +++ b/web/src/pages/flows/FlowListPage.ts @@ -81,7 +81,7 @@ export class FlowListPage extends TablePage { ${gettext("Execute")} - + ${gettext("Export")} `,