diff --git a/authentik/blueprints/v1/importer.py b/authentik/blueprints/v1/importer.py
index 96ff74bdb..e7560e02c 100644
--- a/authentik/blueprints/v1/importer.py
+++ b/authentik/blueprints/v1/importer.py
@@ -187,7 +187,10 @@ class Importer:
if "pk" in updated_identifiers:
model_instance.pk = updated_identifiers["pk"]
serializer_kwargs["instance"] = model_instance
- full_data = self.__update_pks_for_attrs(entry.get_attrs(self.__import))
+ try:
+ full_data = self.__update_pks_for_attrs(entry.get_attrs(self.__import))
+ except ValueError as exc:
+ raise EntryInvalidError(exc) from exc
full_data.update(updated_identifiers)
serializer_kwargs["data"] = full_data
diff --git a/authentik/events/utils.py b/authentik/events/utils.py
index b81da3314..047ae963d 100644
--- a/authentik/events/utils.py
+++ b/authentik/events/utils.py
@@ -14,6 +14,7 @@ from django.views.debug import SafeExceptionReporterFilter
from geoip2.models import City
from guardian.utils import get_anonymous_user
+from authentik.blueprints.v1.common import YAMLTag
from authentik.core.models import User
from authentik.events.geo import GEOIP_READER
from authentik.policies.types import PolicyRequest
@@ -111,6 +112,10 @@ def sanitize_item(value: Any) -> Any:
return GEOIP_READER.city_to_dict(value)
if isinstance(value, Path):
return str(value)
+ if isinstance(value, Exception):
+ return str(value)
+ if isinstance(value, YAMLTag):
+ return str(value)
if isinstance(value, type):
return {
"type": value.__name__,
diff --git a/authentik/flows/api/flows.py b/authentik/flows/api/flows.py
index 638524236..6a76fcb22 100644
--- a/authentik/flows/api/flows.py
+++ b/authentik/flows/api/flows.py
@@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
-from rest_framework.fields import ReadOnlyField
+from rest_framework.fields import BooleanField, DictField, ListField, ReadOnlyField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.response import Response
@@ -24,7 +24,9 @@ from authentik.core.api.utils import (
FilePathSerializer,
FileUploadSerializer,
LinkSerializer,
+ PassiveSerializer,
)
+from authentik.events.utils import sanitize_dict
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
@@ -77,6 +79,13 @@ class FlowSerializer(ModelSerializer):
}
+class FlowImportResultSerializer(PassiveSerializer):
+ """Logs of an attempted flow import"""
+
+ logs = ListField(child=DictField(), read_only=True)
+ success = BooleanField(read_only=True)
+
+
class FlowViewSet(UsedByMixin, ModelViewSet):
"""Flow Viewset"""
@@ -130,25 +139,38 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
@extend_schema(
request={"multipart/form-data": FileUploadSerializer},
responses={
- 204: OpenApiResponse(description="Successfully imported flow"),
- 400: OpenApiResponse(description="Bad request"),
+ 204: FlowImportResultSerializer,
+ 400: FlowImportResultSerializer,
},
)
- @action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
+ @action(url_path="import", detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
def import_flow(self, request: Request) -> Response:
"""Import flow from .yaml file"""
+ import_response = FlowImportResultSerializer(
+ data={
+ "logs": [],
+ "success": False,
+ }
+ )
+ import_response.is_valid()
file = request.FILES.get("file", None)
if not file:
- return HttpResponseBadRequest()
+ return Response(data=import_response.initial_data, status=400)
+
importer = Importer(file.read().decode())
- valid, _logs = importer.validate()
- # TODO: return logs
+ valid, logs = importer.validate()
+ import_response.initial_data["logs"] = [sanitize_dict(log) for log in logs]
+ import_response.initial_data["success"] = valid
+ import_response.is_valid()
if not valid:
- return HttpResponseBadRequest()
+ return Response(data=import_response.initial_data, status=200)
+
successful = importer.apply()
+ import_response.initial_data["success"] = successful
+ import_response.is_valid()
if not successful:
- return HttpResponseBadRequest()
- return Response(status=204)
+ return Response(data=import_response.initial_data, status=200)
+ return Response(data=import_response.initial_data, status=200)
@permission_required(
"authentik_flows.export_flow",
diff --git a/schema.yml b/schema.yml
index c4e940ac5..2330cc223 100644
--- a/schema.yml
+++ b/schema.yml
@@ -7516,9 +7516,9 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
- /flows/instances/import_flow/:
+ /flows/instances/import/:
post:
- operationId: flows_instances_import_flow_create
+ operationId: flows_instances_import_create
description: Import flow from .yaml file
tags:
- flows
@@ -7531,9 +7531,17 @@ paths:
- authentik: []
responses:
'204':
- description: Successfully imported flow
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FlowImportResult'
+ description: ''
'400':
- description: Bad request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FlowImportResult'
+ description: ''
'403':
content:
application/json:
@@ -27610,6 +27618,22 @@ components:
- pending_user_avatar
- request_id
- type
+ FlowImportResult:
+ type: object
+ description: Logs of an attempted flow import
+ properties:
+ logs:
+ type: array
+ items:
+ type: object
+ additionalProperties: {}
+ readOnly: true
+ success:
+ type: boolean
+ readOnly: true
+ required:
+ - logs
+ - success
FlowInspection:
type: object
description: Serializer for inspect endpoint
diff --git a/web/src/admin/flows/FlowImportForm.ts b/web/src/admin/flows/FlowImportForm.ts
index d1060cee5..f1876be3d 100644
--- a/web/src/admin/flows/FlowImportForm.ts
+++ b/web/src/admin/flows/FlowImportForm.ts
@@ -3,30 +3,103 @@ import { SentryIgnoredError } from "@goauthentik/common/errors";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
+
+
import { t } from "@lingui/macro";
-import { TemplateResult, html } from "lit";
-import { customElement } from "lit/decorators.js";
-import { Flow, FlowsApi } from "@goauthentik/api";
+
+import { CSSResult, TemplateResult, html } from "lit";
+import { customElement, state } from "lit/decorators.js";
+
+
+
+import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
+
+
+
+import { Flow, FlowImportResult, FlowsApi } from "@goauthentik/api";
+import { PFColor } from "@goauthentik/elements/Label";
+
@customElement("ak-flow-import-form")
export class FlowImportForm extends Form
+ ${(this.result?.logs || []).length > 0
+ ? this.result?.logs?.map((m) => {
+ return html`
+