Repository: goauthentik/authentik

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>


Discussion:
the main invalid thing in the blueprint above is the authorized URLS:

```yaml
redirect_uris: |
  "https://minio:9000/"
```


Resolving patch:
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 @@ def validate_content(self, content: str) -> str:
         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:


Test patch: