We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Stacktrace when attempting to save an invalid blueprint to internal storage
**Describe the bug**
`POST /api/v3/managed/blueprints/` results in a stacktrace when attempting to upload an invalid blueprint

**To Reproduce**
Steps to reproduce the behavior:
1. Go to 'Customization / Blueprints'
2. Click on Create
3. Paste the included blueprint (which is apparently invalid for some reason) in the code block under "Internal". Name it whatever.
4. Click 'Create' to save, it flashes red but no information is presented.
5. See error in Logs view.

**Expected behavior**
A useful error shows up in the UI and no stack trace in the logs.

**Logs**
<details>
    <summary>Stacktrace from authentik</summary>

```
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 472, in thread_handler
    raise exc_info[1]
  File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
    response = await wrapped_callback(
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 435, in __call__
    ret = await asyncio.wait_for(future, timeout=None)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/asyncio/tasks.py", line 442, in wait_for
    return await fut
           ^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/current_thread_executor.py", line 22, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/asgiref/sync.py", line 476, in thread_handler
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 55, in wrapped_view
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/mixins.py", line 18, in create
    serializer.is_valid(raise_exception=True)
  File "/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py", line 227, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py", line 426, in run_validation
    value = self.to_internal_value(data)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py", line 485, in to_internal_value
    validated_value = validate_method(validated_value)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/authentik/blueprints/api.py", line 52, in validate_content
    raise ValidationError(_("Failed to validate blueprint"), *[x["msg"] for x in logs])
                                                              ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/authentik/blueprints/api.py", line 52, in <listcomp>
    raise ValidationError(_("Failed to validate blueprint"), *[x["msg"] for x in logs])
                                                               ~^^^^^^^
builtins.KeyError: 'msg'
```
</details>


**Version and Deployment (please complete the following information):**
- authentik version: 2023.4.1
- Deployment: podman via personal ansible role

**Additional context**

<details>
    <summary>Invalid blueprint</summary>

```
entries:
  - attrs:
      authorization_flow: !Find [authentik_flows.flow, [slug, "allow-app"]]
      client_type: "confidential"
      property_mappings:
        - !Find [authentik_providers_oauth2.ScopeMapping, [scope_name, "minio admin"]]
        - !Find [authentik_providers_oauth2.ScopeMapping, [scope_name, "email"]]
      redirect_uris:
        - "https://minio:9000/"
    id: "provider/min-io"
    identifiers:
      name: "Min.IO"
    model: "authentik_providers_oauth2.OAuth2Provider"
    state: "present"
  - attrs:
      meta_description: "MinIO is a Object Storage released under AGPL-3.0. It is API compatible with Amazon S3."
      meta_launch_url: "https://minio.example.com/"
      meta_publisher: "MinIO, Inc."
      name: "Min.IO"
      open_in_new_tab: false
      policy_engine_mode: "any"
      provider: !KeyOf provider/min-io
      slug: "min-io"
    id: "app/min-io"
    identifiers:
      slug: "min-io"
    model: "authentik_core.Application"
    state: "present"
  - attrs:
      is_superuser: false
      name: "Min.IO Users"
      parent: null
    id: "group/min-io"
    identifiers:
      name: "Min.IO Users"
    model: "authentik_core.group"
    state: "present"
  - attrs:
      group: !KeyOf group/min-io
      order: 10
      target: !Find [authentik_core.Application, [slug, "min-io"]]
    id: "policy/min-io"
    identifiers:
      group: !KeyOf group/min-io
    model: "authentik_policies.PolicyBinding"
    state: "present"
metadata:
  name: "Application: Min.IO"
version: 1
```
</details>

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `authentik/blueprints/api.py`
Content:
```
1 """Serializer mixin for managed models"""
2 from django.utils.translation import gettext_lazy as _
3 from drf_spectacular.utils import extend_schema, inline_serializer
4 from rest_framework.decorators import action
5 from rest_framework.exceptions import ValidationError
6 from rest_framework.fields import CharField, DateTimeField, JSONField
7 from rest_framework.permissions import IsAdminUser
8 from rest_framework.request import Request
9 from rest_framework.response import Response
10 from rest_framework.serializers import ListSerializer, ModelSerializer
11 from rest_framework.viewsets import ModelViewSet
12 
13 from authentik.api.decorators import permission_required
14 from authentik.blueprints.models import BlueprintInstance, BlueprintRetrievalFailed
15 from authentik.blueprints.v1.importer import Importer
16 from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
17 from authentik.core.api.used_by import UsedByMixin
18 from authentik.core.api.utils import PassiveSerializer
19 
20 
21 class ManagedSerializer:
22     """Managed Serializer"""
23 
24     managed = CharField(read_only=True, allow_null=True)
25 
26 
27 class MetadataSerializer(PassiveSerializer):
28     """Serializer for blueprint metadata"""
29 
30     name = CharField()
31     labels = JSONField()
32 
33 
34 class BlueprintInstanceSerializer(ModelSerializer):
35     """Info about a single blueprint instance file"""
36 
37     def validate_path(self, path: str) -> str:
38         """Ensure the path specified is retrievable"""
39         try:
40             BlueprintInstance(path=path).retrieve()
41         except BlueprintRetrievalFailed as exc:
42             raise ValidationError(exc) from exc
43         return path
44 
45     def validate_content(self, content: str) -> str:
46         """Ensure content (if set) is a valid blueprint"""
47         if content == "":
48             return content
49         context = self.instance.context if self.instance else {}
50         valid, logs = Importer(content, context).validate()
51         if not valid:
52             raise ValidationError(_("Failed to validate blueprint"), *[x["msg"] for x in logs])
53         return content
54 
55     def validate(self, attrs: dict) -> dict:
56         if attrs.get("path", "") == "" and attrs.get("content", "") == "":
57             raise ValidationError(_("Either path or content must be set."))
58         return super().validate(attrs)
59 
60     class Meta:
61         model = BlueprintInstance
62         fields = [
63             "pk",
64             "name",
65             "path",
66             "context",
67             "last_applied",
68             "last_applied_hash",
69             "status",
70             "enabled",
71             "managed_models",
72             "metadata",
73             "content",
74         ]
75         extra_kwargs = {
76             "status": {"read_only": True},
77             "last_applied": {"read_only": True},
78             "last_applied_hash": {"read_only": True},
79             "managed_models": {"read_only": True},
80             "metadata": {"read_only": True},
81         }
82 
83 
84 class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
85     """Blueprint instances"""
86 
87     permission_classes = [IsAdminUser]
88     serializer_class = BlueprintInstanceSerializer
89     queryset = BlueprintInstance.objects.all()
90     search_fields = ["name", "path"]
91     filterset_fields = ["name", "path"]
92 
93     @extend_schema(
94         responses={
95             200: ListSerializer(
96                 child=inline_serializer(
97                     "BlueprintFile",
98                     fields={
99                         "path": CharField(),
100                         "last_m": DateTimeField(),
101                         "hash": CharField(),
102                         "meta": MetadataSerializer(required=False, read_only=True),
103                     },
104                 )
105             )
106         }
107     )
108     @action(detail=False, pagination_class=None, filter_backends=[])
109     def available(self, request: Request) -> Response:
110         """Get blueprints"""
111         files: list[dict] = blueprints_find_dict.delay().get()
112         return Response(files)
113 
114     @permission_required("authentik_blueprints.view_blueprintinstance")
115     @extend_schema(
116         request=None,
117         responses={
118             200: BlueprintInstanceSerializer(),
119         },
120     )
121     @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
122     def apply(self, request: Request, *args, **kwargs) -> Response:
123         """Apply a blueprint"""
124         blueprint = self.get_object()
125         apply_blueprint.delay(str(blueprint.pk)).get()
126         return self.retrieve(request, *args, **kwargs)
127
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/authentik/blueprints/api.py b/authentik/blueprints/api.py
--- a/authentik/blueprints/api.py
+++ b/authentik/blueprints/api.py
@@ -49,7 +49,8 @@
         context = self.instance.context if self.instance else {}
         valid, logs = Importer(content, context).validate()
         if not valid:
-            raise ValidationError(_("Failed to validate blueprint"), *[x["msg"] for x in logs])
+            text_logs = "\n".join([x["event"] for x in logs])
+            raise ValidationError(_("Failed to validate blueprint: %(logs)s" % {"logs": text_logs}))
         return content
 
     def validate(self, attrs: dict) -> dict:

{"golden_diff": "diff --git a/authentik/blueprints/api.py b/authentik/blueprints/api.py\n--- a/authentik/blueprints/api.py\n+++ b/authentik/blueprints/api.py\n@@ -49,7 +49,8 @@\n         context = self.instance.context if self.instance else {}\n         valid, logs = Importer(content, context).validate()\n         if not valid:\n-            raise ValidationError(_(\"Failed to validate blueprint\"), *[x[\"msg\"] for x in logs])\n+            text_logs = \"\\n\".join([x[\"event\"] for x in logs])\n+            raise ValidationError(_(\"Failed to validate blueprint: %(logs)s\" % {\"logs\": text_logs}))\n         return content\n \n     def validate(self, attrs: dict) -> dict:\n", "issue": "Stacktrace when attempting to save an invalid blueprint to internal storage\n**Describe the bug**\r\n`POST /api/v3/managed/blueprints/` results in a stacktrace when attempting to upload an invalid blueprint\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Go to 'Customization / Blueprints'\r\n2. Click on Create\r\n3. Paste the included blueprint (which is apparently invalid for some reason) in the code block under \"Internal\". Name it whatever.\r\n4. Click 'Create' to save, it flashes red but no information is presented.\r\n5. See error in Logs view.\r\n\r\n**Expected behavior**\r\nA useful error shows up in the UI and no stack trace in the logs.\r\n\r\n**Logs**\r\n<details>\r\n    <summary>Stacktrace from authentik</summary>\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.11/site-packages/asgiref/sync.py\", line 472, in thread_handler\r\n    raise exc_info[1]\r\n  File \"/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py\", line 253, in _get_response_async\r\n    response = await wrapped_callback(\r\n               ^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/asgiref/sync.py\", line 435, in __call__\r\n    ret = await asyncio.wait_for(future, timeout=None)\r\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/asyncio/tasks.py\", line 442, in wait_for\r\n    return await fut\r\n           ^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/asgiref/current_thread_executor.py\", line 22, in run\r\n    result = self.fn(*self.args, **self.kwargs)\r\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/asgiref/sync.py\", line 476, in thread_handler\r\n    return func(*args, **kwargs)\r\n           ^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py\", line 55, in wrapped_view\r\n    return view_func(*args, **kwargs)\r\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/viewsets.py\", line 125, in view\r\n    return self.dispatch(request, *args, **kwargs)\r\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/views.py\", line 509, in dispatch\r\n    response = self.handle_exception(exc)\r\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/views.py\", line 469, in handle_exception\r\n    self.raise_uncaught_exception(exc)\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/views.py\", line 480, in raise_uncaught_exception\r\n    raise exc\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/views.py\", line 506, in dispatch\r\n    response = handler(request, *args, **kwargs)\r\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/mixins.py\", line 18, in create\r\n    serializer.is_valid(raise_exception=True)\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py\", line 227, in is_valid\r\n    self._validated_data = self.run_validation(self.initial_data)\r\n                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py\", line 426, in run_validation\r\n    value = self.to_internal_value(data)\r\n            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py\", line 485, in to_internal_value\r\n    validated_value = validate_method(validated_value)\r\n                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/authentik/blueprints/api.py\", line 52, in validate_content\r\n    raise ValidationError(_(\"Failed to validate blueprint\"), *[x[\"msg\"] for x in logs])\r\n                                                              ^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/authentik/blueprints/api.py\", line 52, in <listcomp>\r\n    raise ValidationError(_(\"Failed to validate blueprint\"), *[x[\"msg\"] for x in logs])\r\n                                                               ~^^^^^^^\r\nbuiltins.KeyError: 'msg'\r\n```\r\n</details>\r\n\r\n\r\n**Version and Deployment (please complete the following information):**\r\n- authentik version: 2023.4.1\r\n- Deployment: podman via personal ansible role\r\n\r\n**Additional context**\r\n\r\n<details>\r\n    <summary>Invalid blueprint</summary>\r\n\r\n```\r\nentries:\r\n  - attrs:\r\n      authorization_flow: !Find [authentik_flows.flow, [slug, \"allow-app\"]]\r\n      client_type: \"confidential\"\r\n      property_mappings:\r\n        - !Find [authentik_providers_oauth2.ScopeMapping, [scope_name, \"minio admin\"]]\r\n        - !Find [authentik_providers_oauth2.ScopeMapping, [scope_name, \"email\"]]\r\n      redirect_uris:\r\n        - \"https://minio:9000/\"\r\n    id: \"provider/min-io\"\r\n    identifiers:\r\n      name: \"Min.IO\"\r\n    model: \"authentik_providers_oauth2.OAuth2Provider\"\r\n    state: \"present\"\r\n  - attrs:\r\n      meta_description: \"MinIO is a Object Storage released under AGPL-3.0. It is API compatible with Amazon S3.\"\r\n      meta_launch_url: \"https://minio.example.com/\"\r\n      meta_publisher: \"MinIO, Inc.\"\r\n      name: \"Min.IO\"\r\n      open_in_new_tab: false\r\n      policy_engine_mode: \"any\"\r\n      provider: !KeyOf provider/min-io\r\n      slug: \"min-io\"\r\n    id: \"app/min-io\"\r\n    identifiers:\r\n      slug: \"min-io\"\r\n    model: \"authentik_core.Application\"\r\n    state: \"present\"\r\n  - attrs:\r\n      is_superuser: false\r\n      name: \"Min.IO Users\"\r\n      parent: null\r\n    id: \"group/min-io\"\r\n    identifiers:\r\n      name: \"Min.IO Users\"\r\n    model: \"authentik_core.group\"\r\n    state: \"present\"\r\n  - attrs:\r\n      group: !KeyOf group/min-io\r\n      order: 10\r\n      target: !Find [authentik_core.Application, [slug, \"min-io\"]]\r\n    id: \"policy/min-io\"\r\n    identifiers:\r\n      group: !KeyOf group/min-io\r\n    model: \"authentik_policies.PolicyBinding\"\r\n    state: \"present\"\r\nmetadata:\r\n  name: \"Application: Min.IO\"\r\nversion: 1\r\n```\r\n</details>\n", "before_files": [{"content": "\"\"\"Serializer mixin for managed models\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema, inline_serializer\nfrom rest_framework.decorators import action\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.fields import CharField, DateTimeField, JSONField\nfrom rest_framework.permissions import IsAdminUser\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ListSerializer, ModelSerializer\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom authentik.api.decorators import permission_required\nfrom authentik.blueprints.models import BlueprintInstance, BlueprintRetrievalFailed\nfrom authentik.blueprints.v1.importer import Importer\nfrom authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.core.api.utils import PassiveSerializer\n\n\nclass ManagedSerializer:\n    \"\"\"Managed Serializer\"\"\"\n\n    managed = CharField(read_only=True, allow_null=True)\n\n\nclass MetadataSerializer(PassiveSerializer):\n    \"\"\"Serializer for blueprint metadata\"\"\"\n\n    name = CharField()\n    labels = JSONField()\n\n\nclass BlueprintInstanceSerializer(ModelSerializer):\n    \"\"\"Info about a single blueprint instance file\"\"\"\n\n    def validate_path(self, path: str) -> str:\n        \"\"\"Ensure the path specified is retrievable\"\"\"\n        try:\n            BlueprintInstance(path=path).retrieve()\n        except BlueprintRetrievalFailed as exc:\n            raise ValidationError(exc) from exc\n        return path\n\n    def validate_content(self, content: str) -> str:\n        \"\"\"Ensure content (if set) is a valid blueprint\"\"\"\n        if content == \"\":\n            return content\n        context = self.instance.context if self.instance else {}\n        valid, logs = Importer(content, context).validate()\n        if not valid:\n            raise ValidationError(_(\"Failed to validate blueprint\"), *[x[\"msg\"] for x in logs])\n        return content\n\n    def validate(self, attrs: dict) -> dict:\n        if attrs.get(\"path\", \"\") == \"\" and attrs.get(\"content\", \"\") == \"\":\n            raise ValidationError(_(\"Either path or content must be set.\"))\n        return super().validate(attrs)\n\n    class Meta:\n        model = BlueprintInstance\n        fields = [\n            \"pk\",\n            \"name\",\n            \"path\",\n            \"context\",\n            \"last_applied\",\n            \"last_applied_hash\",\n            \"status\",\n            \"enabled\",\n            \"managed_models\",\n            \"metadata\",\n            \"content\",\n        ]\n        extra_kwargs = {\n            \"status\": {\"read_only\": True},\n            \"last_applied\": {\"read_only\": True},\n            \"last_applied_hash\": {\"read_only\": True},\n            \"managed_models\": {\"read_only\": True},\n            \"metadata\": {\"read_only\": True},\n        }\n\n\nclass BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"Blueprint instances\"\"\"\n\n    permission_classes = [IsAdminUser]\n    serializer_class = BlueprintInstanceSerializer\n    queryset = BlueprintInstance.objects.all()\n    search_fields = [\"name\", \"path\"]\n    filterset_fields = [\"name\", \"path\"]\n\n    @extend_schema(\n        responses={\n            200: ListSerializer(\n                child=inline_serializer(\n                    \"BlueprintFile\",\n                    fields={\n                        \"path\": CharField(),\n                        \"last_m\": DateTimeField(),\n                        \"hash\": CharField(),\n                        \"meta\": MetadataSerializer(required=False, read_only=True),\n                    },\n                )\n            )\n        }\n    )\n    @action(detail=False, pagination_class=None, filter_backends=[])\n    def available(self, request: Request) -> Response:\n        \"\"\"Get blueprints\"\"\"\n        files: list[dict] = blueprints_find_dict.delay().get()\n        return Response(files)\n\n    @permission_required(\"authentik_blueprints.view_blueprintinstance\")\n    @extend_schema(\n        request=None,\n        responses={\n            200: BlueprintInstanceSerializer(),\n        },\n    )\n    @action(detail=True, pagination_class=None, filter_backends=[], methods=[\"POST\"])\n    def apply(self, request: Request, *args, **kwargs) -> Response:\n        \"\"\"Apply a blueprint\"\"\"\n        blueprint = self.get_object()\n        apply_blueprint.delay(str(blueprint.pk)).get()\n        return self.retrieve(request, *args, **kwargs)\n", "path": "authentik/blueprints/api.py"}], "after_files": [{"content": "\"\"\"Serializer mixin for managed models\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema, inline_serializer\nfrom rest_framework.decorators import action\nfrom rest_framework.exceptions import ValidationError\nfrom rest_framework.fields import CharField, DateTimeField, JSONField\nfrom rest_framework.permissions import IsAdminUser\nfrom rest_framework.request import Request\nfrom rest_framework.response import Response\nfrom rest_framework.serializers import ListSerializer, ModelSerializer\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom authentik.api.decorators import permission_required\nfrom authentik.blueprints.models import BlueprintInstance, BlueprintRetrievalFailed\nfrom authentik.blueprints.v1.importer import Importer\nfrom authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict\nfrom authentik.core.api.used_by import UsedByMixin\nfrom authentik.core.api.utils import PassiveSerializer\n\n\nclass ManagedSerializer:\n    \"\"\"Managed Serializer\"\"\"\n\n    managed = CharField(read_only=True, allow_null=True)\n\n\nclass MetadataSerializer(PassiveSerializer):\n    \"\"\"Serializer for blueprint metadata\"\"\"\n\n    name = CharField()\n    labels = JSONField()\n\n\nclass BlueprintInstanceSerializer(ModelSerializer):\n    \"\"\"Info about a single blueprint instance file\"\"\"\n\n    def validate_path(self, path: str) -> str:\n        \"\"\"Ensure the path specified is retrievable\"\"\"\n        try:\n            BlueprintInstance(path=path).retrieve()\n        except BlueprintRetrievalFailed as exc:\n            raise ValidationError(exc) from exc\n        return path\n\n    def validate_content(self, content: str) -> str:\n        \"\"\"Ensure content (if set) is a valid blueprint\"\"\"\n        if content == \"\":\n            return content\n        context = self.instance.context if self.instance else {}\n        valid, logs = Importer(content, context).validate()\n        if not valid:\n            text_logs = \"\\n\".join([x[\"event\"] for x in logs])\n            raise ValidationError(_(\"Failed to validate blueprint: %(logs)s\" % {\"logs\": text_logs}))\n        return content\n\n    def validate(self, attrs: dict) -> dict:\n        if attrs.get(\"path\", \"\") == \"\" and attrs.get(\"content\", \"\") == \"\":\n            raise ValidationError(_(\"Either path or content must be set.\"))\n        return super().validate(attrs)\n\n    class Meta:\n        model = BlueprintInstance\n        fields = [\n            \"pk\",\n            \"name\",\n            \"path\",\n            \"context\",\n            \"last_applied\",\n            \"last_applied_hash\",\n            \"status\",\n            \"enabled\",\n            \"managed_models\",\n            \"metadata\",\n            \"content\",\n        ]\n        extra_kwargs = {\n            \"status\": {\"read_only\": True},\n            \"last_applied\": {\"read_only\": True},\n            \"last_applied_hash\": {\"read_only\": True},\n            \"managed_models\": {\"read_only\": True},\n            \"metadata\": {\"read_only\": True},\n        }\n\n\nclass BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):\n    \"\"\"Blueprint instances\"\"\"\n\n    permission_classes = [IsAdminUser]\n    serializer_class = BlueprintInstanceSerializer\n    queryset = BlueprintInstance.objects.all()\n    search_fields = [\"name\", \"path\"]\n    filterset_fields = [\"name\", \"path\"]\n\n    @extend_schema(\n        responses={\n            200: ListSerializer(\n                child=inline_serializer(\n                    \"BlueprintFile\",\n                    fields={\n                        \"path\": CharField(),\n                        \"last_m\": DateTimeField(),\n                        \"hash\": CharField(),\n                        \"meta\": MetadataSerializer(required=False, read_only=True),\n                    },\n                )\n            )\n        }\n    )\n    @action(detail=False, pagination_class=None, filter_backends=[])\n    def available(self, request: Request) -> Response:\n        \"\"\"Get blueprints\"\"\"\n        files: list[dict] = blueprints_find_dict.delay().get()\n        return Response(files)\n\n    @permission_required(\"authentik_blueprints.view_blueprintinstance\")\n    @extend_schema(\n        request=None,\n        responses={\n            200: BlueprintInstanceSerializer(),\n        },\n    )\n    @action(detail=True, pagination_class=None, filter_backends=[], methods=[\"POST\"])\n    def apply(self, request: Request, *args, **kwargs) -> Response:\n        \"\"\"Apply a blueprint\"\"\"\n        blueprint = self.get_object()\n        apply_blueprint.delay(str(blueprint.pk)).get()\n        return self.retrieve(request, *args, **kwargs)\n", "path": "authentik/blueprints/api.py"}]}