sources: add custom icon support (#4022)
* add source icon Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add to oauth form Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add to other browser sources Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add migration, return icon in UI challenges Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * deduplicate file upload Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -23,10 +23,15 @@ from authentik.admin.api.metrics import CoordinateSerializer | ||||
| from authentik.api.decorators import permission_required | ||||
| from authentik.core.api.providers import ProviderSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import FilePathSerializer, FileUploadSerializer | ||||
| from authentik.core.models import Application, User | ||||
| from authentik.events.models import EventAction | ||||
| from authentik.events.utils import sanitize_dict | ||||
| from authentik.lib.utils.file import ( | ||||
|     FilePathSerializer, | ||||
|     FileUploadSerializer, | ||||
|     set_file, | ||||
|     set_file_url, | ||||
| ) | ||||
| from authentik.policies.api.exec import PolicyTestResultSerializer | ||||
| from authentik.policies.engine import PolicyEngine | ||||
| from authentik.policies.types import PolicyResult | ||||
| @ -224,21 +229,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|     def set_icon(self, request: Request, slug: str): | ||||
|         """Set application icon""" | ||||
|         app: Application = self.get_object() | ||||
|         icon = request.FILES.get("file", None) | ||||
|         clear = request.data.get("clear", "false").lower() == "true" | ||||
|         if clear: | ||||
|             # .delete() saves the model by default | ||||
|             app.meta_icon.delete() | ||||
|             return Response({}) | ||||
|         if icon: | ||||
|             app.meta_icon = icon | ||||
|             try: | ||||
|                 app.save() | ||||
|             except PermissionError as exc: | ||||
|                 LOGGER.warning("Failed to save icon", exc=exc) | ||||
|                 return HttpResponseBadRequest() | ||||
|             return Response({}) | ||||
|         return HttpResponseBadRequest() | ||||
|         return set_file(request, app, "meta_icon") | ||||
|  | ||||
|     @permission_required("authentik_core.change_application") | ||||
|     @extend_schema( | ||||
| @ -258,12 +249,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|     def set_icon_url(self, request: Request, slug: str): | ||||
|         """Set application icon (as URL)""" | ||||
|         app: Application = self.get_object() | ||||
|         url = request.data.get("url", None) | ||||
|         if url is None: | ||||
|             return HttpResponseBadRequest() | ||||
|         app.meta_icon.name = url | ||||
|         app.save() | ||||
|         return Response({}) | ||||
|         return set_file_url(request, app, "meta_icon") | ||||
|  | ||||
|     @permission_required("authentik_core.view_application", ["authentik_events.view_event"]) | ||||
|     @extend_schema(responses={200: CoordinateSerializer(many=True)}) | ||||
|  | ||||
| @ -2,10 +2,11 @@ | ||||
| from typing import Iterable | ||||
|  | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from drf_spectacular.utils import extend_schema | ||||
| from drf_spectacular.utils import OpenApiResponse, extend_schema | ||||
| from rest_framework import mixins | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.filters import OrderingFilter, SearchFilter | ||||
| from rest_framework.parsers import MultiPartParser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, ReadOnlyField, SerializerMethodField | ||||
| @ -13,10 +14,17 @@ from rest_framework.viewsets import GenericViewSet | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions | ||||
| from authentik.api.decorators import permission_required | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.core.models import Source, UserSourceConnection | ||||
| from authentik.core.types import UserSettingSerializer | ||||
| from authentik.lib.utils.file import ( | ||||
|     FilePathSerializer, | ||||
|     FileUploadSerializer, | ||||
|     set_file, | ||||
|     set_file_url, | ||||
| ) | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
| from authentik.policies.engine import PolicyEngine | ||||
|  | ||||
| @ -28,6 +36,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer): | ||||
|  | ||||
|     managed = ReadOnlyField() | ||||
|     component = SerializerMethodField() | ||||
|     icon = ReadOnlyField(source="get_icon") | ||||
|  | ||||
|     def get_component(self, obj: Source) -> str: | ||||
|         """Get object component so that we know how to edit the object""" | ||||
| @ -54,6 +63,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer): | ||||
|             "user_matching_mode", | ||||
|             "managed", | ||||
|             "user_path_template", | ||||
|             "icon", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @ -75,6 +85,49 @@ class SourceViewSet( | ||||
|     def get_queryset(self):  # pragma: no cover | ||||
|         return Source.objects.select_subclasses() | ||||
|  | ||||
|     @permission_required("authentik_core.change_source") | ||||
|     @extend_schema( | ||||
|         request={ | ||||
|             "multipart/form-data": FileUploadSerializer, | ||||
|         }, | ||||
|         responses={ | ||||
|             200: OpenApiResponse(description="Success"), | ||||
|             400: OpenApiResponse(description="Bad request"), | ||||
|         }, | ||||
|     ) | ||||
|     @action( | ||||
|         detail=True, | ||||
|         pagination_class=None, | ||||
|         filter_backends=[], | ||||
|         methods=["POST"], | ||||
|         parser_classes=(MultiPartParser,), | ||||
|     ) | ||||
|     # pylint: disable=unused-argument | ||||
|     def set_icon(self, request: Request, slug: str): | ||||
|         """Set source icon""" | ||||
|         source: Source = self.get_object() | ||||
|         return set_file(request, source, "icon") | ||||
|  | ||||
|     @permission_required("authentik_core.change_source") | ||||
|     @extend_schema( | ||||
|         request=FilePathSerializer, | ||||
|         responses={ | ||||
|             200: OpenApiResponse(description="Success"), | ||||
|             400: OpenApiResponse(description="Bad request"), | ||||
|         }, | ||||
|     ) | ||||
|     @action( | ||||
|         detail=True, | ||||
|         pagination_class=None, | ||||
|         filter_backends=[], | ||||
|         methods=["POST"], | ||||
|     ) | ||||
|     # pylint: disable=unused-argument | ||||
|     def set_icon_url(self, request: Request, slug: str): | ||||
|         """Set source icon (as URL)""" | ||||
|         source: Source = self.get_object() | ||||
|         return set_file_url(request, source, "icon") | ||||
|  | ||||
|     @extend_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||
|     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||
|     def types(self, request: Request) -> Response: | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| from typing import Any | ||||
|  | ||||
| from django.db.models import Model | ||||
| from rest_framework.fields import BooleanField, CharField, FileField, IntegerField | ||||
| from rest_framework.fields import CharField, IntegerField | ||||
| from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError | ||||
|  | ||||
|  | ||||
| @ -23,19 +23,6 @@ class PassiveSerializer(Serializer): | ||||
|         return Model() | ||||
|  | ||||
|  | ||||
| class FileUploadSerializer(PassiveSerializer): | ||||
|     """Serializer to upload file""" | ||||
|  | ||||
|     file = FileField(required=False) | ||||
|     clear = BooleanField(default=False) | ||||
|  | ||||
|  | ||||
| class FilePathSerializer(PassiveSerializer): | ||||
|     """Serializer to upload file""" | ||||
|  | ||||
|     url = CharField() | ||||
|  | ||||
|  | ||||
| class MetaNameSerializer(PassiveSerializer): | ||||
|     """Add verbose names to response""" | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L