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:
parent
1e15d1f538
commit
9f5fb692ba
|
@ -23,10 +23,15 @@ from authentik.admin.api.metrics import CoordinateSerializer
|
||||||
from authentik.api.decorators import permission_required
|
from authentik.api.decorators import permission_required
|
||||||
from authentik.core.api.providers import ProviderSerializer
|
from authentik.core.api.providers import ProviderSerializer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
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.core.models import Application, User
|
||||||
from authentik.events.models import EventAction
|
from authentik.events.models import EventAction
|
||||||
from authentik.events.utils import sanitize_dict
|
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.api.exec import PolicyTestResultSerializer
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.types import PolicyResult
|
from authentik.policies.types import PolicyResult
|
||||||
|
@ -224,21 +229,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||||
def set_icon(self, request: Request, slug: str):
|
def set_icon(self, request: Request, slug: str):
|
||||||
"""Set application icon"""
|
"""Set application icon"""
|
||||||
app: Application = self.get_object()
|
app: Application = self.get_object()
|
||||||
icon = request.FILES.get("file", None)
|
return set_file(request, app, "meta_icon")
|
||||||
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()
|
|
||||||
|
|
||||||
@permission_required("authentik_core.change_application")
|
@permission_required("authentik_core.change_application")
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -258,12 +249,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||||
def set_icon_url(self, request: Request, slug: str):
|
def set_icon_url(self, request: Request, slug: str):
|
||||||
"""Set application icon (as URL)"""
|
"""Set application icon (as URL)"""
|
||||||
app: Application = self.get_object()
|
app: Application = self.get_object()
|
||||||
url = request.data.get("url", None)
|
return set_file_url(request, app, "meta_icon")
|
||||||
if url is None:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
app.meta_icon.name = url
|
|
||||||
app.save()
|
|
||||||
return Response({})
|
|
||||||
|
|
||||||
@permission_required("authentik_core.view_application", ["authentik_events.view_event"])
|
@permission_required("authentik_core.view_application", ["authentik_events.view_event"])
|
||||||
@extend_schema(responses={200: CoordinateSerializer(many=True)})
|
@extend_schema(responses={200: CoordinateSerializer(many=True)})
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
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 import mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer, ReadOnlyField, SerializerMethodField
|
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 structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
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.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
|
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
|
||||||
from authentik.core.models import Source, UserSourceConnection
|
from authentik.core.models import Source, UserSourceConnection
|
||||||
from authentik.core.types import UserSettingSerializer
|
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.lib.utils.reflection import all_subclasses
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
|
|
||||||
|
@ -28,6 +36,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||||
|
|
||||||
managed = ReadOnlyField()
|
managed = ReadOnlyField()
|
||||||
component = SerializerMethodField()
|
component = SerializerMethodField()
|
||||||
|
icon = ReadOnlyField(source="get_icon")
|
||||||
|
|
||||||
def get_component(self, obj: Source) -> str:
|
def get_component(self, obj: Source) -> str:
|
||||||
"""Get object component so that we know how to edit the object"""
|
"""Get object component so that we know how to edit the object"""
|
||||||
|
@ -54,6 +63,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||||
"user_matching_mode",
|
"user_matching_mode",
|
||||||
"managed",
|
"managed",
|
||||||
"user_path_template",
|
"user_path_template",
|
||||||
|
"icon",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,6 +85,49 @@ class SourceViewSet(
|
||||||
def get_queryset(self): # pragma: no cover
|
def get_queryset(self): # pragma: no cover
|
||||||
return Source.objects.select_subclasses()
|
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)})
|
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||||
@action(detail=False, pagination_class=None, filter_backends=[])
|
@action(detail=False, pagination_class=None, filter_backends=[])
|
||||||
def types(self, request: Request) -> Response:
|
def types(self, request: Request) -> Response:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.db.models import Model
|
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
|
from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,19 +23,6 @@ class PassiveSerializer(Serializer):
|
||||||
return Model()
|
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):
|
class MetaNameSerializer(PassiveSerializer):
|
||||||
"""Add verbose names to response"""
|
"""Add verbose names to response"""
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-15 20:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_core", "0023_source_authentik_c_slug_ccb2e5_idx_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="source",
|
||||||
|
name="icon",
|
||||||
|
field=models.FileField(
|
||||||
|
default=None, max_length=500, null=True, upload_to="source-icons/"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -421,6 +421,12 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
|
||||||
|
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True)
|
property_mappings = models.ManyToManyField("PropertyMapping", default=None, blank=True)
|
||||||
|
icon = models.FileField(
|
||||||
|
upload_to="source-icons/",
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
max_length=500,
|
||||||
|
)
|
||||||
|
|
||||||
authentication_flow = models.ForeignKey(
|
authentication_flow = models.ForeignKey(
|
||||||
"authentik_flows.Flow",
|
"authentik_flows.Flow",
|
||||||
|
@ -454,6 +460,16 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
|
||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_icon(self) -> Optional[str]:
|
||||||
|
"""Get the URL to the Icon. If the name is /static or
|
||||||
|
starts with http it is returned as-is"""
|
||||||
|
if not self.icon:
|
||||||
|
return None
|
||||||
|
if "://" in self.icon.name or self.icon.name.startswith("/static"):
|
||||||
|
return self.icon.name
|
||||||
|
return self.icon.url
|
||||||
|
|
||||||
def get_user_path(self) -> str:
|
def get_user_path(self) -> str:
|
||||||
"""Get user path, fallback to default for formatting errors"""
|
"""Get user path, fallback to default for formatting errors"""
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""Flow API Views"""
|
"""Flow API Views"""
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.http.response import HttpResponseBadRequest
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
@ -19,19 +18,19 @@ from authentik.api.decorators import permission_required
|
||||||
from authentik.blueprints.v1.exporter import FlowExporter
|
from authentik.blueprints.v1.exporter import FlowExporter
|
||||||
from authentik.blueprints.v1.importer import Importer
|
from authentik.blueprints.v1.importer import Importer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import (
|
from authentik.core.api.utils import CacheSerializer, LinkSerializer, PassiveSerializer
|
||||||
CacheSerializer,
|
|
||||||
FilePathSerializer,
|
|
||||||
FileUploadSerializer,
|
|
||||||
LinkSerializer,
|
|
||||||
PassiveSerializer,
|
|
||||||
)
|
|
||||||
from authentik.events.utils import sanitize_dict
|
from authentik.events.utils import sanitize_dict
|
||||||
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
|
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
|
||||||
from authentik.flows.exceptions import FlowNonApplicableException
|
from authentik.flows.exceptions import FlowNonApplicableException
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
|
||||||
from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN
|
from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN
|
||||||
|
from authentik.lib.utils.file import (
|
||||||
|
FilePathSerializer,
|
||||||
|
FileUploadSerializer,
|
||||||
|
set_file,
|
||||||
|
set_file_url,
|
||||||
|
)
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
@ -249,25 +248,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
|
||||||
def set_background(self, request: Request, slug: str):
|
def set_background(self, request: Request, slug: str):
|
||||||
"""Set Flow background"""
|
"""Set Flow background"""
|
||||||
flow: Flow = self.get_object()
|
flow: Flow = self.get_object()
|
||||||
background = request.FILES.get("file", None)
|
return set_file(request, flow, "background")
|
||||||
clear = request.data.get("clear", "false").lower() == "true"
|
|
||||||
if clear:
|
|
||||||
if flow.background_url.startswith("/media"):
|
|
||||||
# .delete() saves the model by default
|
|
||||||
flow.background.delete()
|
|
||||||
else:
|
|
||||||
flow.background = None
|
|
||||||
flow.save()
|
|
||||||
return Response({})
|
|
||||||
if background:
|
|
||||||
flow.background = background
|
|
||||||
try:
|
|
||||||
flow.save()
|
|
||||||
except PermissionError as exc:
|
|
||||||
LOGGER.warning("Failed to save icon", exc=exc)
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
return Response({})
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
@permission_required("authentik_core.change_application")
|
@permission_required("authentik_core.change_application")
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -287,12 +268,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
|
||||||
def set_background_url(self, request: Request, slug: str):
|
def set_background_url(self, request: Request, slug: str):
|
||||||
"""Set Flow background (as URL)"""
|
"""Set Flow background (as URL)"""
|
||||||
flow: Flow = self.get_object()
|
flow: Flow = self.get_object()
|
||||||
url = request.data.get("url", None)
|
return set_file_url(request, flow, "background")
|
||||||
if not url:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
flow.background.name = url
|
|
||||||
flow.save()
|
|
||||||
return Response({})
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""file utils"""
|
||||||
|
from django.db.models import Model
|
||||||
|
from django.http import HttpResponseBadRequest
|
||||||
|
from rest_framework.fields import BooleanField, CharField, FileField
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class FileUploadSerializer(PassiveSerializer):
|
||||||
|
"""Serializer to upload file"""
|
||||||
|
|
||||||
|
file = FileField(required=False)
|
||||||
|
clear = BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
class FilePathSerializer(PassiveSerializer):
|
||||||
|
"""Serializer to upload file"""
|
||||||
|
|
||||||
|
url = CharField()
|
||||||
|
|
||||||
|
|
||||||
|
def set_file(request: Request, obj: Model, field: str):
|
||||||
|
"""Upload file"""
|
||||||
|
field = getattr(obj, field)
|
||||||
|
icon = request.FILES.get("file", None)
|
||||||
|
clear = request.data.get("clear", "false").lower() == "true"
|
||||||
|
if clear:
|
||||||
|
# .delete() saves the model by default
|
||||||
|
field.delete()
|
||||||
|
return Response({})
|
||||||
|
if icon:
|
||||||
|
field = icon
|
||||||
|
try:
|
||||||
|
obj.save()
|
||||||
|
except PermissionError as exc:
|
||||||
|
LOGGER.warning("Failed to save file", exc=exc)
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
return Response({})
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
|
||||||
|
def set_file_url(request: Request, obj: Model, field: str):
|
||||||
|
"""Set file field to URL"""
|
||||||
|
field = getattr(obj, field)
|
||||||
|
url = request.data.get("url", None)
|
||||||
|
if url is None:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
field.name = url
|
||||||
|
obj.save()
|
||||||
|
return Response({})
|
|
@ -35,6 +35,7 @@ class OAuthSourceSerializer(SourceSerializer):
|
||||||
|
|
||||||
provider_type = ChoiceField(choices=registry.get_name_tuple())
|
provider_type = ChoiceField(choices=registry.get_name_tuple())
|
||||||
callback_url = SerializerMethodField()
|
callback_url = SerializerMethodField()
|
||||||
|
type = SerializerMethodField()
|
||||||
|
|
||||||
def get_callback_url(self, instance: OAuthSource) -> str:
|
def get_callback_url(self, instance: OAuthSource) -> str:
|
||||||
"""Get OAuth Callback URL"""
|
"""Get OAuth Callback URL"""
|
||||||
|
@ -46,8 +47,6 @@ class OAuthSourceSerializer(SourceSerializer):
|
||||||
return relative_url
|
return relative_url
|
||||||
return self.context["request"].build_absolute_uri(relative_url)
|
return self.context["request"].build_absolute_uri(relative_url)
|
||||||
|
|
||||||
type = SerializerMethodField()
|
|
||||||
|
|
||||||
@extend_schema_field(SourceTypeSerializer)
|
@extend_schema_field(SourceTypeSerializer)
|
||||||
def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:
|
def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:
|
||||||
"""Get source's type configuration"""
|
"""Get source's type configuration"""
|
||||||
|
|
|
@ -75,15 +75,20 @@ class OAuthSource(Source):
|
||||||
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
||||||
provider_type = self.type
|
provider_type = self.type
|
||||||
provider = provider_type()
|
provider = provider_type()
|
||||||
|
icon = self.get_icon
|
||||||
|
if not icon:
|
||||||
|
icon = provider.icon_url()
|
||||||
return UILoginButton(
|
return UILoginButton(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
icon_url=provider.icon_url(),
|
|
||||||
challenge=provider.login_challenge(self, request),
|
challenge=provider.login_challenge(self, request),
|
||||||
|
icon_url=icon,
|
||||||
)
|
)
|
||||||
|
|
||||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||||
provider_type = self.type
|
provider_type = self.type
|
||||||
provider = provider_type()
|
icon = self.get_icon
|
||||||
|
if not icon:
|
||||||
|
icon = provider_type().icon_url()
|
||||||
return UserSettingSerializer(
|
return UserSettingSerializer(
|
||||||
data={
|
data={
|
||||||
"title": self.name,
|
"title": self.name,
|
||||||
|
@ -92,7 +97,7 @@ class OAuthSource(Source):
|
||||||
"authentik_sources_oauth:oauth-client-login",
|
"authentik_sources_oauth:oauth-client-login",
|
||||||
kwargs={"source_slug": self.slug},
|
kwargs={"source_slug": self.slug},
|
||||||
),
|
),
|
||||||
"icon_url": provider.icon_url(),
|
"icon_url": icon,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,9 @@ class PlexSource(Source):
|
||||||
return PlexSourceSerializer
|
return PlexSourceSerializer
|
||||||
|
|
||||||
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
|
||||||
|
icon = self.get_icon
|
||||||
|
if not icon:
|
||||||
|
icon = static("authentik/sources/plex.svg")
|
||||||
return UILoginButton(
|
return UILoginButton(
|
||||||
challenge=PlexAuthenticationChallenge(
|
challenge=PlexAuthenticationChallenge(
|
||||||
{
|
{
|
||||||
|
@ -73,17 +76,20 @@ class PlexSource(Source):
|
||||||
"slug": self.slug,
|
"slug": self.slug,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
icon_url=static("authentik/sources/plex.svg"),
|
icon_url=icon,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||||
|
icon = self.get_icon
|
||||||
|
if not icon:
|
||||||
|
icon = static("authentik/sources/plex.svg")
|
||||||
return UserSettingSerializer(
|
return UserSettingSerializer(
|
||||||
data={
|
data={
|
||||||
"title": self.name,
|
"title": self.name,
|
||||||
"component": "ak-user-settings-source-plex",
|
"component": "ak-user-settings-source-plex",
|
||||||
"configure_url": self.client_id,
|
"configure_url": self.client_id,
|
||||||
"icon_url": static("authentik/sources/plex.svg"),
|
"icon_url": icon,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,27 @@ class SAMLSourceViewSet(UsedByMixin, ModelViewSet):
|
||||||
queryset = SAMLSource.objects.all()
|
queryset = SAMLSource.objects.all()
|
||||||
serializer_class = SAMLSourceSerializer
|
serializer_class = SAMLSourceSerializer
|
||||||
lookup_field = "slug"
|
lookup_field = "slug"
|
||||||
filterset_fields = "__all__"
|
filterset_fields = [
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"enabled",
|
||||||
|
"authentication_flow",
|
||||||
|
"enrollment_flow",
|
||||||
|
"managed",
|
||||||
|
"policy_engine_mode",
|
||||||
|
"user_matching_mode",
|
||||||
|
"pre_authentication_flow",
|
||||||
|
"issuer",
|
||||||
|
"sso_url",
|
||||||
|
"slo_url",
|
||||||
|
"allow_idp_initiated",
|
||||||
|
"name_id_policy",
|
||||||
|
"binding_type",
|
||||||
|
"signing_kp",
|
||||||
|
"digest_algorithm",
|
||||||
|
"signature_algorithm",
|
||||||
|
"temporary_user_delete_after",
|
||||||
|
]
|
||||||
search_fields = ["name", "slug"]
|
search_fields = ["name", "slug"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
||||||
|
|
|
@ -191,9 +191,13 @@ class SAMLSource(Source):
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
name=self.name,
|
name=self.name,
|
||||||
|
icon_url=self.get_icon,
|
||||||
)
|
)
|
||||||
|
|
||||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||||
|
icon = self.get_icon
|
||||||
|
if not icon:
|
||||||
|
icon = static(f"authentik/sources/{self.slug}.svg")
|
||||||
return UserSettingSerializer(
|
return UserSettingSerializer(
|
||||||
data={
|
data={
|
||||||
"title": self.name,
|
"title": self.name,
|
||||||
|
@ -202,7 +206,7 @@ class SAMLSource(Source):
|
||||||
"authentik_sources_saml:login",
|
"authentik_sources_saml:login",
|
||||||
kwargs={"source_slug": self.slug},
|
kwargs={"source_slug": self.slug},
|
||||||
),
|
),
|
||||||
"icon_url": static(f"authentik/sources/{self.slug}.svg"),
|
"icon_url": icon,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
115
schema.yml
115
schema.yml
|
@ -15795,6 +15795,69 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/sources/all/{slug}/set_icon/:
|
||||||
|
post:
|
||||||
|
operationId: sources_all_set_icon_create
|
||||||
|
description: Set source icon
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: slug
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Internal source name, used in URLs.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- sources
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/FileUploadRequest'
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
'400':
|
||||||
|
description: Bad request
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/sources/all/{slug}/set_icon_url/:
|
||||||
|
post:
|
||||||
|
operationId: sources_all_set_icon_url_create
|
||||||
|
description: Set source icon (as URL)
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: slug
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Internal source name, used in URLs.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- sources
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/FilePathRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
'400':
|
||||||
|
description: Bad request
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/sources/all/{slug}/used_by/:
|
/sources/all/{slug}/used_by/:
|
||||||
get:
|
get:
|
||||||
operationId: sources_all_used_by_list
|
operationId: sources_all_used_by_list
|
||||||
|
@ -17092,20 +17155,6 @@ paths:
|
||||||
description: Number of results to return per page.
|
description: Number of results to return per page.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
- in: query
|
|
||||||
name: pbm_uuid
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
- in: query
|
|
||||||
name: policies
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
explode: true
|
|
||||||
style: form
|
|
||||||
- in: query
|
- in: query
|
||||||
name: policy_engine_mode
|
name: policy_engine_mode
|
||||||
schema:
|
schema:
|
||||||
|
@ -17118,15 +17167,6 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
- in: query
|
|
||||||
name: property_mappings
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
explode: true
|
|
||||||
style: form
|
|
||||||
- name: search
|
- name: search
|
||||||
required: false
|
required: false
|
||||||
in: query
|
in: query
|
||||||
|
@ -17176,10 +17216,6 @@ paths:
|
||||||
- username_link
|
- username_link
|
||||||
description: How the source determines if an existing user should be authenticated
|
description: How the source determines if an existing user should be authenticated
|
||||||
or a new user enrolled.
|
or a new user enrolled.
|
||||||
- in: query
|
|
||||||
name: user_path_template
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
tags:
|
tags:
|
||||||
- sources
|
- sources
|
||||||
security:
|
security:
|
||||||
|
@ -28871,6 +28907,10 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
user_path_template:
|
user_path_template:
|
||||||
type: string
|
type: string
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
readOnly: true
|
||||||
server_uri:
|
server_uri:
|
||||||
type: string
|
type: string
|
||||||
format: uri
|
format: uri
|
||||||
|
@ -28933,6 +28973,7 @@ components:
|
||||||
required:
|
required:
|
||||||
- base_dn
|
- base_dn
|
||||||
- component
|
- component
|
||||||
|
- icon
|
||||||
- managed
|
- managed
|
||||||
- meta_model_name
|
- meta_model_name
|
||||||
- name
|
- name
|
||||||
|
@ -29662,6 +29703,10 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
user_path_template:
|
user_path_template:
|
||||||
type: string
|
type: string
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
readOnly: true
|
||||||
provider_type:
|
provider_type:
|
||||||
$ref: '#/components/schemas/ProviderTypeEnum'
|
$ref: '#/components/schemas/ProviderTypeEnum'
|
||||||
request_token_url:
|
request_token_url:
|
||||||
|
@ -29707,6 +29752,7 @@ components:
|
||||||
- callback_url
|
- callback_url
|
||||||
- component
|
- component
|
||||||
- consumer_key
|
- consumer_key
|
||||||
|
- icon
|
||||||
- managed
|
- managed
|
||||||
- meta_model_name
|
- meta_model_name
|
||||||
- name
|
- name
|
||||||
|
@ -35075,6 +35121,10 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
user_path_template:
|
user_path_template:
|
||||||
type: string
|
type: string
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
readOnly: true
|
||||||
client_id:
|
client_id:
|
||||||
type: string
|
type: string
|
||||||
description: Client identifier used to talk to Plex.
|
description: Client identifier used to talk to Plex.
|
||||||
|
@ -35092,6 +35142,7 @@ components:
|
||||||
description: Plex token used to check friends
|
description: Plex token used to check friends
|
||||||
required:
|
required:
|
||||||
- component
|
- component
|
||||||
|
- icon
|
||||||
- managed
|
- managed
|
||||||
- meta_model_name
|
- meta_model_name
|
||||||
- name
|
- name
|
||||||
|
@ -36495,6 +36546,10 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
user_path_template:
|
user_path_template:
|
||||||
type: string
|
type: string
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
readOnly: true
|
||||||
pre_authentication_flow:
|
pre_authentication_flow:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
@ -36543,6 +36598,7 @@ components:
|
||||||
doesn''t log out manually. (Format: hours=1;minutes=2;seconds=3).'
|
doesn''t log out manually. (Format: hours=1;minutes=2;seconds=3).'
|
||||||
required:
|
required:
|
||||||
- component
|
- component
|
||||||
|
- icon
|
||||||
- managed
|
- managed
|
||||||
- meta_model_name
|
- meta_model_name
|
||||||
- name
|
- name
|
||||||
|
@ -36936,8 +36992,13 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
user_path_template:
|
user_path_template:
|
||||||
type: string
|
type: string
|
||||||
|
icon:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- component
|
- component
|
||||||
|
- icon
|
||||||
- managed
|
- managed
|
||||||
- meta_model_name
|
- meta_model_name
|
||||||
- name
|
- name
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -9,11 +9,12 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
import { until } from "lit/directives/until.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
CapabilitiesEnum,
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
OAuthSource,
|
OAuthSource,
|
||||||
|
@ -57,6 +58,9 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
providerType: SourceType | null = null;
|
providerType: SourceType | null = null;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
clearIcon = false;
|
||||||
|
|
||||||
getSuccessMessage(): string {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return t`Successfully updated source.`;
|
return t`Successfully updated source.`;
|
||||||
|
@ -65,18 +69,38 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send = (data: OAuthSource): Promise<OAuthSource> => {
|
send = async (data: OAuthSource): Promise<OAuthSource> => {
|
||||||
data.providerType = (this.providerType?.slug || "") as ProviderTypeEnum;
|
data.providerType = (this.providerType?.slug || "") as ProviderTypeEnum;
|
||||||
if (this.instance?.slug) {
|
let source: OAuthSource;
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthPartialUpdate({
|
if (this.instance) {
|
||||||
|
source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthPartialUpdate({
|
||||||
slug: this.instance.slug,
|
slug: this.instance.slug,
|
||||||
patchedOAuthSourceRequest: data,
|
patchedOAuthSourceRequest: data,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthCreate({
|
source = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthCreate({
|
||||||
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
|
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const c = await config();
|
||||||
|
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||||
|
const icon = this.getFormFiles()["icon"];
|
||||||
|
if (icon || this.clearIcon) {
|
||||||
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||||
|
slug: source.slug,
|
||||||
|
file: icon,
|
||||||
|
clear: this.clearIcon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconUrlCreate({
|
||||||
|
slug: source.slug,
|
||||||
|
filePathRequest: {
|
||||||
|
url: data.icon || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return source;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderUrlOptions(): TemplateResult {
|
renderUrlOptions(): TemplateResult {
|
||||||
|
@ -282,6 +306,54 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
|
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
${until(
|
||||||
|
config().then((c) => {
|
||||||
|
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||||
|
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
|
${this.instance?.icon
|
||||||
|
? html`
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Currently set to:`} ${this.instance?.icon}
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
${this.instance?.icon
|
||||||
|
? html`
|
||||||
|
<ak-form-element-horizontal>
|
||||||
|
<div class="pf-c-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="pf-c-check__input"
|
||||||
|
@change=${(ev: Event) => {
|
||||||
|
const target = ev.target as HTMLInputElement;
|
||||||
|
this.clearIcon = target.checked;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label class="pf-c-check__label">
|
||||||
|
${t`Clear icon`}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Delete currently set icon.`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
`
|
||||||
|
: html``}`;
|
||||||
|
}
|
||||||
|
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${first(this.instance?.icon, "")}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
|
||||||
<ak-form-group .expanded=${true}>
|
<ak-form-group .expanded=${true}>
|
||||||
<span slot="header"> ${t`Protocol settings`} </span>
|
<span slot="header"> ${t`Protocol settings`} </span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
||||||
import { first, randomString } from "@goauthentik/common/utils";
|
import { first, randomString } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -9,11 +9,12 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
import { until } from "lit/directives/until.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
CapabilitiesEnum,
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
PlexSource,
|
PlexSource,
|
||||||
|
@ -35,6 +36,9 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@state()
|
||||||
|
clearIcon = false;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
plexToken?: string;
|
plexToken?: string;
|
||||||
|
|
||||||
|
@ -55,18 +59,38 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send = (data: PlexSource): Promise<PlexSource> => {
|
send = async (data: PlexSource): Promise<PlexSource> => {
|
||||||
data.plexToken = this.plexToken || "";
|
data.plexToken = this.plexToken || "";
|
||||||
if (this.instance?.slug) {
|
let source: PlexSource;
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesPlexUpdate({
|
if (this.instance) {
|
||||||
|
source = await new SourcesApi(DEFAULT_CONFIG).sourcesPlexUpdate({
|
||||||
slug: this.instance.slug,
|
slug: this.instance.slug,
|
||||||
plexSourceRequest: data,
|
plexSourceRequest: data,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesPlexCreate({
|
source = await new SourcesApi(DEFAULT_CONFIG).sourcesPlexCreate({
|
||||||
plexSourceRequest: data,
|
plexSourceRequest: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const c = await config();
|
||||||
|
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||||
|
const icon = this.getFormFiles()["icon"];
|
||||||
|
if (icon || this.clearIcon) {
|
||||||
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||||
|
slug: source.slug,
|
||||||
|
file: icon,
|
||||||
|
clear: this.clearIcon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconUrlCreate({
|
||||||
|
slug: source.slug,
|
||||||
|
filePathRequest: {
|
||||||
|
url: data.icon || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return source;
|
||||||
};
|
};
|
||||||
|
|
||||||
async doAuth(): Promise<void> {
|
async doAuth(): Promise<void> {
|
||||||
|
@ -229,6 +253,54 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
||||||
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
|
${t`Path template for users created. Use placeholders like \`%(slug)s\` to insert the source slug.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
${until(
|
||||||
|
config().then((c) => {
|
||||||
|
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||||
|
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
|
${this.instance?.icon
|
||||||
|
? html`
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Currently set to:`} ${this.instance?.icon}
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
${this.instance?.icon
|
||||||
|
? html`
|
||||||
|
<ak-form-element-horizontal>
|
||||||
|
<div class="pf-c-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="pf-c-check__input"
|
||||||
|
@change=${(ev: Event) => {
|
||||||
|
const target = ev.target as HTMLInputElement;
|
||||||
|
this.clearIcon = target.checked;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label class="pf-c-check__label">
|
||||||
|
${t`Clear icon`}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Delete currently set icon.`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
`
|
||||||
|
: html``}`;
|
||||||
|
}
|
||||||
|
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${first(this.instance?.icon, "")}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
|
||||||
<ak-form-group .expanded=${true}>
|
<ak-form-group .expanded=${true}>
|
||||||
<span slot="header"> ${t`Protocol settings`} </span>
|
<span slot="header"> ${t`Protocol settings`} </span>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -9,12 +9,13 @@ import "@goauthentik/elements/utils/TimeDeltaHelp";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
import { until } from "lit/directives/until.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BindingTypeEnum,
|
BindingTypeEnum,
|
||||||
|
CapabilitiesEnum,
|
||||||
CryptoApi,
|
CryptoApi,
|
||||||
DigestAlgorithmEnum,
|
DigestAlgorithmEnum,
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
|
@ -28,6 +29,9 @@ import {
|
||||||
|
|
||||||
@customElement("ak-source-saml-form")
|
@customElement("ak-source-saml-form")
|
||||||
export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
|
@state()
|
||||||
|
clearIcon = false;
|
||||||
|
|
||||||
loadInstance(pk: string): Promise<SAMLSource> {
|
loadInstance(pk: string): Promise<SAMLSource> {
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlRetrieve({
|
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlRetrieve({
|
||||||
slug: pk,
|
slug: pk,
|
||||||
|
@ -42,17 +46,37 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send = (data: SAMLSource): Promise<SAMLSource> => {
|
send = async (data: SAMLSource): Promise<SAMLSource> => {
|
||||||
|
let source: SAMLSource;
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlUpdate({
|
source = await new SourcesApi(DEFAULT_CONFIG).sourcesSamlUpdate({
|
||||||
slug: this.instance.slug,
|
slug: this.instance.slug,
|
||||||
sAMLSourceRequest: data,
|
sAMLSourceRequest: data,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return new SourcesApi(DEFAULT_CONFIG).sourcesSamlCreate({
|
source = await new SourcesApi(DEFAULT_CONFIG).sourcesSamlCreate({
|
||||||
sAMLSourceRequest: data,
|
sAMLSourceRequest: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const c = await config();
|
||||||
|
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||||
|
const icon = this.getFormFiles()["icon"];
|
||||||
|
if (icon || this.clearIcon) {
|
||||||
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||||
|
slug: source.slug,
|
||||||
|
file: icon,
|
||||||
|
clear: this.clearIcon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconUrlCreate({
|
||||||
|
slug: source.slug,
|
||||||
|
filePathRequest: {
|
||||||
|
url: data.icon || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return source;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderForm(): TemplateResult {
|
renderForm(): TemplateResult {
|
||||||
|
@ -126,6 +150,54 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
${until(
|
||||||
|
config().then((c) => {
|
||||||
|
if (c.capabilities.includes(CapabilitiesEnum.SaveMedia)) {
|
||||||
|
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input type="file" value="" class="pf-c-form-control" />
|
||||||
|
${this.instance?.icon
|
||||||
|
? html`
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Currently set to:`} ${this.instance?.icon}
|
||||||
|
</p>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
${this.instance?.icon
|
||||||
|
? html`
|
||||||
|
<ak-form-element-horizontal>
|
||||||
|
<div class="pf-c-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="pf-c-check__input"
|
||||||
|
@change=${(ev: Event) => {
|
||||||
|
const target = ev.target as HTMLInputElement;
|
||||||
|
this.clearIcon = target.checked;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label class="pf-c-check__label">
|
||||||
|
${t`Clear icon`}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Delete currently set icon.`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
`
|
||||||
|
: html``}`;
|
||||||
|
}
|
||||||
|
return html`<ak-form-element-horizontal label=${t`Icon`} name="icon">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${first(this.instance?.icon, "")}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>`;
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
|
||||||
<ak-form-group .expanded=${true}>
|
<ak-form-group .expanded=${true}>
|
||||||
<span slot="header"> ${t`Protocol settings`} </span>
|
<span slot="header"> ${t`Protocol settings`} </span>
|
||||||
|
|
Reference in New Issue