Merge branch 'main' into celery-2-dramatiq
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
2
.github/workflows/ci-main-daily.yml
vendored
2
.github/workflows/ci-main-daily.yml
vendored
@ -15,8 +15,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
version:
|
version:
|
||||||
- docs
|
- docs
|
||||||
|
- version-2025-4
|
||||||
- version-2025-2
|
- version-2025-2
|
||||||
- version-2024-12
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -2,7 +2,7 @@ name: "CodeQL"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, "*", next, version*]
|
branches: [main, next, version*]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
schedule:
|
schedule:
|
||||||
|
@ -75,7 +75,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
|||||||
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 4: Download uv
|
# Stage 4: Download uv
|
||||||
FROM ghcr.io/astral-sh/uv:0.7.13 AS uv
|
FROM ghcr.io/astral-sh/uv:0.7.14 AS uv
|
||||||
# Stage 5: Base python image
|
# Stage 5: Base python image
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
|
@ -35,6 +35,6 @@ def blueprint_tester(file_name: Path) -> Callable:
|
|||||||
|
|
||||||
|
|
||||||
for blueprint_file in Path("blueprints/").glob("**/*.yaml"):
|
for blueprint_file in Path("blueprints/").glob("**/*.yaml"):
|
||||||
if "local" in str(blueprint_file):
|
if "local" in str(blueprint_file) or "testing" in str(blueprint_file):
|
||||||
continue
|
continue
|
||||||
setattr(TestPackaged, f"test_blueprint_{blueprint_file}", blueprint_tester(blueprint_file))
|
setattr(TestPackaged, f"test_blueprint_{blueprint_file}", blueprint_tester(blueprint_file))
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
"""Authenticator Devices API Views"""
|
"""Authenticator Devices API Views"""
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from drf_spectacular.utils import extend_schema
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework.fields import (
|
from rest_framework.fields import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
@ -15,6 +13,7 @@ from rest_framework.request import Request
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ViewSet
|
from rest_framework.viewsets import ViewSet
|
||||||
|
|
||||||
|
from authentik.core.api.users import ParamUserSerializer
|
||||||
from authentik.core.api.utils import MetaNameSerializer
|
from authentik.core.api.utils import MetaNameSerializer
|
||||||
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import EndpointDevice
|
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import EndpointDevice
|
||||||
from authentik.stages.authenticator import device_classes, devices_for_user
|
from authentik.stages.authenticator import device_classes, devices_for_user
|
||||||
@ -23,7 +22,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
|||||||
|
|
||||||
|
|
||||||
class DeviceSerializer(MetaNameSerializer):
|
class DeviceSerializer(MetaNameSerializer):
|
||||||
"""Serializer for Duo authenticator devices"""
|
"""Serializer for authenticator devices"""
|
||||||
|
|
||||||
pk = CharField()
|
pk = CharField()
|
||||||
name = CharField()
|
name = CharField()
|
||||||
@ -33,22 +32,27 @@ class DeviceSerializer(MetaNameSerializer):
|
|||||||
last_updated = DateTimeField(read_only=True)
|
last_updated = DateTimeField(read_only=True)
|
||||||
last_used = DateTimeField(read_only=True, allow_null=True)
|
last_used = DateTimeField(read_only=True, allow_null=True)
|
||||||
extra_description = SerializerMethodField()
|
extra_description = SerializerMethodField()
|
||||||
|
external_id = SerializerMethodField()
|
||||||
|
|
||||||
def get_type(self, instance: Device) -> str:
|
def get_type(self, instance: Device) -> str:
|
||||||
"""Get type of device"""
|
"""Get type of device"""
|
||||||
return instance._meta.label
|
return instance._meta.label
|
||||||
|
|
||||||
def get_extra_description(self, instance: Device) -> str:
|
def get_extra_description(self, instance: Device) -> str | None:
|
||||||
"""Get extra description"""
|
"""Get extra description"""
|
||||||
if isinstance(instance, WebAuthnDevice):
|
if isinstance(instance, WebAuthnDevice):
|
||||||
return (
|
return instance.device_type.description if instance.device_type else None
|
||||||
instance.device_type.description
|
|
||||||
if instance.device_type
|
|
||||||
else _("Extra description not available")
|
|
||||||
)
|
|
||||||
if isinstance(instance, EndpointDevice):
|
if isinstance(instance, EndpointDevice):
|
||||||
return instance.data.get("deviceSignals", {}).get("deviceModel")
|
return instance.data.get("deviceSignals", {}).get("deviceModel")
|
||||||
return ""
|
return None
|
||||||
|
|
||||||
|
def get_external_id(self, instance: Device) -> str | None:
|
||||||
|
"""Get external Device ID"""
|
||||||
|
if isinstance(instance, WebAuthnDevice):
|
||||||
|
return instance.device_type.aaguid if instance.device_type else None
|
||||||
|
if isinstance(instance, EndpointDevice):
|
||||||
|
return instance.data.get("deviceSignals", {}).get("deviceModel")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class DeviceViewSet(ViewSet):
|
class DeviceViewSet(ViewSet):
|
||||||
@ -57,7 +61,6 @@ class DeviceViewSet(ViewSet):
|
|||||||
serializer_class = DeviceSerializer
|
serializer_class = DeviceSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
@extend_schema(responses={200: DeviceSerializer(many=True)})
|
|
||||||
def list(self, request: Request) -> Response:
|
def list(self, request: Request) -> Response:
|
||||||
"""Get all devices for current user"""
|
"""Get all devices for current user"""
|
||||||
devices = devices_for_user(request.user)
|
devices = devices_for_user(request.user)
|
||||||
@ -79,18 +82,11 @@ class AdminDeviceViewSet(ViewSet):
|
|||||||
yield from device_set
|
yield from device_set
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
parameters=[
|
parameters=[ParamUserSerializer],
|
||||||
OpenApiParameter(
|
|
||||||
name="user",
|
|
||||||
location=OpenApiParameter.QUERY,
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
responses={200: DeviceSerializer(many=True)},
|
responses={200: DeviceSerializer(many=True)},
|
||||||
)
|
)
|
||||||
def list(self, request: Request) -> Response:
|
def list(self, request: Request) -> Response:
|
||||||
"""Get all devices for current user"""
|
"""Get all devices for current user"""
|
||||||
kwargs = {}
|
args = ParamUserSerializer(data=request.query_params)
|
||||||
if "user" in request.query_params:
|
args.is_valid(raise_exception=True)
|
||||||
kwargs = {"user": request.query_params["user"]}
|
return Response(DeviceSerializer(self.get_devices(**args.validated_data), many=True).data)
|
||||||
return Response(DeviceSerializer(self.get_devices(**kwargs), many=True).data)
|
|
||||||
|
@ -90,6 +90,12 @@ from authentik.stages.email.utils import TemplateEmailMessage
|
|||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class ParamUserSerializer(PassiveSerializer):
|
||||||
|
"""Partial serializer for query parameters to select a user"""
|
||||||
|
|
||||||
|
user = PrimaryKeyRelatedField(queryset=User.objects.all().exclude_anonymous(), required=False)
|
||||||
|
|
||||||
|
|
||||||
class UserGroupSerializer(ModelSerializer):
|
class UserGroupSerializer(ModelSerializer):
|
||||||
"""Simplified Group Serializer for user's groups"""
|
"""Simplified Group Serializer for user's groups"""
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
|
|
||||||
from django.contrib.auth.signals import user_logged_out
|
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.http.request import HttpRequest
|
|
||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
|
|
||||||
from authentik.core.models import (
|
from authentik.core.models import (
|
||||||
|
@ -19,7 +19,7 @@ from authentik.blueprints.v1.importer import excluded_models
|
|||||||
from authentik.core.models import Group, User
|
from authentik.core.models import Group, User
|
||||||
from authentik.events.models import Event, EventAction, Notification
|
from authentik.events.models import Event, EventAction, Notification
|
||||||
from authentik.events.utils import model_to_dict
|
from authentik.events.utils import model_to_dict
|
||||||
from authentik.lib.sentry import before_send
|
from authentik.lib.sentry import should_ignore_exception
|
||||||
from authentik.lib.utils.errors import exception_to_string
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
from authentik.stages.authenticator_static.models import StaticToken
|
from authentik.stages.authenticator_static.models import StaticToken
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ class AuditMiddleware:
|
|||||||
message=exception_to_string(exception),
|
message=exception_to_string(exception),
|
||||||
)
|
)
|
||||||
thread.run()
|
thread.run()
|
||||||
elif before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
elif not should_ignore_exception(exception):
|
||||||
thread = EventNewThread(
|
thread = EventNewThread(
|
||||||
EventAction.SYSTEM_EXCEPTION,
|
EventAction.SYSTEM_EXCEPTION,
|
||||||
request,
|
request,
|
||||||
|
@ -4,8 +4,10 @@ from unittest.mock import MagicMock, PropertyMock, patch
|
|||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.test import override_settings
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
from authentik.core.models import Group, User
|
from authentik.core.models import Group, User
|
||||||
from authentik.core.tests.utils import create_test_flow, create_test_user
|
from authentik.core.tests.utils import create_test_flow, create_test_user
|
||||||
@ -648,3 +650,25 @@ class TestFlowExecutor(FlowTestCase):
|
|||||||
self.assertStageResponse(response, flow, component="ak-stage-identification")
|
self.assertStageResponse(response, flow, component="ak-stage-identification")
|
||||||
response = self.client.post(exec_url, {"uid_field": user_other.username}, follow=True)
|
response = self.client.post(exec_url, {"uid_field": user_other.username}, follow=True)
|
||||||
self.assertStageResponse(response, flow, component="ak-stage-access-denied")
|
self.assertStageResponse(response, flow, component="ak-stage-access-denied")
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"authentik.flows.views.executor.to_stage_response",
|
||||||
|
TO_STAGE_RESPONSE_MOCK,
|
||||||
|
)
|
||||||
|
def test_invalid_json(self):
|
||||||
|
"""Test invalid JSON body"""
|
||||||
|
flow = create_test_flow()
|
||||||
|
FlowStageBinding.objects.create(
|
||||||
|
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
|
||||||
|
)
|
||||||
|
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
|
||||||
|
|
||||||
|
with override_settings(TEST=False, DEBUG=False):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.post(url, data="{", content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
with self.assertRaises(ParseError):
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.post(url, data="{", content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -55,7 +55,7 @@ from authentik.flows.planner import (
|
|||||||
FlowPlanner,
|
FlowPlanner,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import AccessDeniedStage, StageView
|
from authentik.flows.stage import AccessDeniedStage, StageView
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException, should_ignore_exception
|
||||||
from authentik.lib.utils.errors import exception_to_string
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
||||||
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
||||||
@ -234,12 +234,13 @@ class FlowExecutorView(APIView):
|
|||||||
"""Handle exception in stage execution"""
|
"""Handle exception in stage execution"""
|
||||||
if settings.DEBUG or settings.TEST:
|
if settings.DEBUG or settings.TEST:
|
||||||
raise exc
|
raise exc
|
||||||
capture_exception(exc)
|
|
||||||
self._logger.warning(exc)
|
self._logger.warning(exc)
|
||||||
Event.new(
|
if not should_ignore_exception(exc):
|
||||||
action=EventAction.SYSTEM_EXCEPTION,
|
capture_exception(exc)
|
||||||
message=exception_to_string(exc),
|
Event.new(
|
||||||
).from_http(self.request)
|
action=EventAction.SYSTEM_EXCEPTION,
|
||||||
|
message=exception_to_string(exc),
|
||||||
|
).from_http(self.request)
|
||||||
challenge = FlowErrorChallenge(self.request, exc)
|
challenge = FlowErrorChallenge(self.request, exc)
|
||||||
challenge.is_valid(raise_exception=True)
|
challenge.is_valid(raise_exception=True)
|
||||||
return to_stage_response(self.request, HttpChallengeResponse(challenge))
|
return to_stage_response(self.request, HttpChallengeResponse(challenge))
|
||||||
|
@ -12,6 +12,7 @@ from django_redis.exceptions import ConnectionInterrupted
|
|||||||
from docker.errors import DockerException
|
from docker.errors import DockerException
|
||||||
from h11 import LocalProtocolError
|
from h11 import LocalProtocolError
|
||||||
from ldap3.core.exceptions import LDAPException
|
from ldap3.core.exceptions import LDAPException
|
||||||
|
from psycopg.errors import Error
|
||||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||||
from redis.exceptions import RedisError, ResponseError
|
from redis.exceptions import RedisError, ResponseError
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
@ -41,6 +42,45 @@ class SentryIgnoredException(Exception):
|
|||||||
"""Base Class for all errors that are suppressed, and not sent to sentry."""
|
"""Base Class for all errors that are suppressed, and not sent to sentry."""
|
||||||
|
|
||||||
|
|
||||||
|
ignored_classes = (
|
||||||
|
# Inbuilt types
|
||||||
|
KeyboardInterrupt,
|
||||||
|
ConnectionResetError,
|
||||||
|
OSError,
|
||||||
|
PermissionError,
|
||||||
|
# Django Errors
|
||||||
|
Error,
|
||||||
|
ImproperlyConfigured,
|
||||||
|
DatabaseError,
|
||||||
|
OperationalError,
|
||||||
|
InternalError,
|
||||||
|
ProgrammingError,
|
||||||
|
SuspiciousOperation,
|
||||||
|
ValidationError,
|
||||||
|
# Redis errors
|
||||||
|
RedisConnectionError,
|
||||||
|
ConnectionInterrupted,
|
||||||
|
RedisError,
|
||||||
|
ResponseError,
|
||||||
|
# websocket errors
|
||||||
|
ChannelFull,
|
||||||
|
WebSocketException,
|
||||||
|
LocalProtocolError,
|
||||||
|
# rest_framework error
|
||||||
|
APIException,
|
||||||
|
# custom baseclass
|
||||||
|
SentryIgnoredException,
|
||||||
|
# ldap errors
|
||||||
|
LDAPException,
|
||||||
|
# Docker errors
|
||||||
|
DockerException,
|
||||||
|
# End-user errors
|
||||||
|
Http404,
|
||||||
|
# AsyncIO
|
||||||
|
CancelledError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SentryTransport(HttpTransport):
|
class SentryTransport(HttpTransport):
|
||||||
"""Custom sentry transport with custom user-agent"""
|
"""Custom sentry transport with custom user-agent"""
|
||||||
|
|
||||||
@ -97,57 +137,21 @@ def traces_sampler(sampling_context: dict) -> float:
|
|||||||
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
return float(CONFIG.get("error_reporting.sample_rate", 0.1))
|
||||||
|
|
||||||
|
|
||||||
|
def should_ignore_exception(exc: Exception) -> bool:
|
||||||
|
"""Check if an exception should be dropped"""
|
||||||
|
return isinstance(exc, ignored_classes)
|
||||||
|
|
||||||
|
|
||||||
def before_send(event: dict, hint: dict) -> dict | None:
|
def before_send(event: dict, hint: dict) -> dict | None:
|
||||||
"""Check if error is database error, and ignore if so"""
|
"""Check if error is database error, and ignore if so"""
|
||||||
|
|
||||||
from psycopg.errors import Error
|
|
||||||
|
|
||||||
ignored_classes = (
|
|
||||||
# Inbuilt types
|
|
||||||
KeyboardInterrupt,
|
|
||||||
ConnectionResetError,
|
|
||||||
OSError,
|
|
||||||
PermissionError,
|
|
||||||
# Django Errors
|
|
||||||
Error,
|
|
||||||
ImproperlyConfigured,
|
|
||||||
DatabaseError,
|
|
||||||
OperationalError,
|
|
||||||
InternalError,
|
|
||||||
ProgrammingError,
|
|
||||||
SuspiciousOperation,
|
|
||||||
ValidationError,
|
|
||||||
# Redis errors
|
|
||||||
RedisConnectionError,
|
|
||||||
ConnectionInterrupted,
|
|
||||||
RedisError,
|
|
||||||
ResponseError,
|
|
||||||
# websocket errors
|
|
||||||
ChannelFull,
|
|
||||||
WebSocketException,
|
|
||||||
LocalProtocolError,
|
|
||||||
# rest_framework error
|
|
||||||
APIException,
|
|
||||||
# custom baseclass
|
|
||||||
SentryIgnoredException,
|
|
||||||
# ldap errors
|
|
||||||
LDAPException,
|
|
||||||
# Docker errors
|
|
||||||
DockerException,
|
|
||||||
# End-user errors
|
|
||||||
Http404,
|
|
||||||
# AsyncIO
|
|
||||||
CancelledError,
|
|
||||||
)
|
|
||||||
exc_value = None
|
exc_value = None
|
||||||
if "exc_info" in hint:
|
if "exc_info" in hint:
|
||||||
_, exc_value, _ = hint["exc_info"]
|
_, exc_value, _ = hint["exc_info"]
|
||||||
if isinstance(exc_value, ignored_classes):
|
if should_ignore_exception(exc_value):
|
||||||
LOGGER.debug("dropping exception", exc=exc_value)
|
LOGGER.debug("dropping exception", exc=exc_value)
|
||||||
return None
|
return None
|
||||||
if "logger" in event:
|
if "logger" in event:
|
||||||
if event["logger"] in [
|
if event["logger"] in [
|
||||||
"kombu",
|
|
||||||
"asyncio",
|
"asyncio",
|
||||||
"multiprocessing",
|
"multiprocessing",
|
||||||
"django_redis",
|
"django_redis",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from authentik.lib.sentry import SentryIgnoredException, before_send
|
from authentik.lib.sentry import SentryIgnoredException, should_ignore_exception
|
||||||
|
|
||||||
|
|
||||||
class TestSentry(TestCase):
|
class TestSentry(TestCase):
|
||||||
@ -10,8 +10,8 @@ class TestSentry(TestCase):
|
|||||||
|
|
||||||
def test_error_not_sent(self):
|
def test_error_not_sent(self):
|
||||||
"""Test SentryIgnoredError not sent"""
|
"""Test SentryIgnoredError not sent"""
|
||||||
self.assertIsNone(before_send({}, {"exc_info": (0, SentryIgnoredException(), 0)}))
|
self.assertTrue(should_ignore_exception(SentryIgnoredException()))
|
||||||
|
|
||||||
def test_error_sent(self):
|
def test_error_sent(self):
|
||||||
"""Test error sent"""
|
"""Test error sent"""
|
||||||
self.assertEqual({}, before_send({}, {"exc_info": (0, ValueError(), 0)}))
|
self.assertFalse(should_ignore_exception(ValueError()))
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from django.contrib.auth.signals import user_logged_out
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models.signals import post_delete, post_save, pre_delete
|
from django.db.models.signals import post_delete, post_save, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.http import HttpRequest
|
|
||||||
|
|
||||||
from authentik.core.models import AuthenticatedSession, User
|
from authentik.core.models import AuthenticatedSession
|
||||||
from authentik.providers.rac.api.endpoints import user_endpoint_cache_key
|
from authentik.providers.rac.api.endpoints import user_endpoint_cache_key
|
||||||
from authentik.providers.rac.consumer_client import (
|
from authentik.providers.rac.consumer_client import (
|
||||||
RAC_CLIENT_GROUP_SESSION,
|
RAC_CLIENT_GROUP_SESSION,
|
||||||
|
@ -5,7 +5,6 @@ from itertools import batched
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from pydanticscim.group import GroupMember
|
from pydanticscim.group import GroupMember
|
||||||
from pydanticscim.responses import PatchOp
|
|
||||||
|
|
||||||
from authentik.core.models import Group
|
from authentik.core.models import Group
|
||||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||||
@ -20,7 +19,12 @@ from authentik.providers.scim.clients.base import SCIMClient
|
|||||||
from authentik.providers.scim.clients.exceptions import (
|
from authentik.providers.scim.clients.exceptions import (
|
||||||
SCIMRequestException,
|
SCIMRequestException,
|
||||||
)
|
)
|
||||||
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchOperation, PatchRequest
|
from authentik.providers.scim.clients.schema import (
|
||||||
|
SCIM_GROUP_SCHEMA,
|
||||||
|
PatchOp,
|
||||||
|
PatchOperation,
|
||||||
|
PatchRequest,
|
||||||
|
)
|
||||||
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
|
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
|
||||||
from authentik.providers.scim.models import (
|
from authentik.providers.scim.models import (
|
||||||
SCIMMapping,
|
SCIMMapping,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""Custom SCIM schemas"""
|
"""Custom SCIM schemas"""
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from pydanticscim.group import Group as BaseGroup
|
from pydanticscim.group import Group as BaseGroup
|
||||||
from pydanticscim.responses import PatchOperation as BasePatchOperation
|
from pydanticscim.responses import PatchOperation as BasePatchOperation
|
||||||
@ -65,6 +67,21 @@ class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PatchOp(str, Enum):
|
||||||
|
|
||||||
|
replace = "replace"
|
||||||
|
remove = "remove"
|
||||||
|
add = "add"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _missing_(cls, value):
|
||||||
|
value = value.lower()
|
||||||
|
for member in cls:
|
||||||
|
if member.lower() == value:
|
||||||
|
return member
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class PatchRequest(BasePatchRequest):
|
class PatchRequest(BasePatchRequest):
|
||||||
"""PatchRequest which correctly sets schemas"""
|
"""PatchRequest which correctly sets schemas"""
|
||||||
|
|
||||||
@ -74,6 +91,7 @@ class PatchRequest(BasePatchRequest):
|
|||||||
class PatchOperation(BasePatchOperation):
|
class PatchOperation(BasePatchOperation):
|
||||||
"""PatchOperation with optional path"""
|
"""PatchOperation with optional path"""
|
||||||
|
|
||||||
|
op: PatchOp
|
||||||
path: str | None
|
path: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ from structlog.stdlib import get_logger
|
|||||||
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
|
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
|
||||||
|
|
||||||
from authentik import get_full_version
|
from authentik import get_full_version
|
||||||
from authentik.lib.sentry import before_send
|
from authentik.lib.sentry import should_ignore_exception
|
||||||
from authentik.lib.utils.errors import exception_to_string
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
|
|
||||||
# set the default Django settings module for the 'celery' program.
|
# set the default Django settings module for the 'celery' program.
|
||||||
@ -80,7 +80,7 @@ def task_error_hook(task_id: str, exception: Exception, traceback, *args, **kwar
|
|||||||
|
|
||||||
LOGGER.warning("Task failure", task_id=task_id.replace("-", ""), exc=exception)
|
LOGGER.warning("Task failure", task_id=task_id.replace("-", ""), exc=exception)
|
||||||
CTX_TASK_ID.set(...)
|
CTX_TASK_ID.set(...)
|
||||||
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
if not should_ignore_exception(exception):
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.SYSTEM_EXCEPTION,
|
EventAction.SYSTEM_EXCEPTION,
|
||||||
message=exception_to_string(exception),
|
message=exception_to_string(exception),
|
||||||
|
277
authentik/sources/scim/tests/test_groups.py
Normal file
277
authentik/sources/scim/tests/test_groups.py
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
"""Test SCIM Group"""
|
||||||
|
|
||||||
|
from json import dumps
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from authentik.core.models import Group
|
||||||
|
from authentik.core.tests.utils import create_test_user
|
||||||
|
from authentik.events.models import Event, EventAction
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
|
||||||
|
from authentik.sources.scim.models import (
|
||||||
|
SCIMSource,
|
||||||
|
SCIMSourceGroup,
|
||||||
|
)
|
||||||
|
from authentik.sources.scim.views.v2.base import SCIM_CONTENT_TYPE
|
||||||
|
|
||||||
|
|
||||||
|
class TestSCIMGroups(APITestCase):
|
||||||
|
"""Test SCIM Group view"""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.source = SCIMSource.objects.create(name=generate_id(), slug=generate_id())
|
||||||
|
|
||||||
|
def test_group_list(self):
|
||||||
|
"""Test full group list"""
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_group_list_single(self):
|
||||||
|
"""Test full group list (single group)"""
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
user = create_test_user()
|
||||||
|
group.users.add(user)
|
||||||
|
SCIMSourceGroup.objects.create(
|
||||||
|
source=self.source,
|
||||||
|
group=group,
|
||||||
|
id=str(uuid4()),
|
||||||
|
)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
"group_id": str(group.pk),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, second=200)
|
||||||
|
SCIMGroupSchema.model_validate_json(response.content, strict=True)
|
||||||
|
|
||||||
|
def test_group_create(self):
|
||||||
|
"""Test group create"""
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data=dumps({"displayName": generate_id(), "externalId": ext_id}),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
|
||||||
|
self.assertTrue(
|
||||||
|
Event.objects.filter(
|
||||||
|
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_group_create_members(self):
|
||||||
|
"""Test group create"""
|
||||||
|
user = create_test_user()
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data=dumps(
|
||||||
|
{
|
||||||
|
"displayName": generate_id(),
|
||||||
|
"externalId": ext_id,
|
||||||
|
"members": [{"value": str(user.uuid)}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
|
||||||
|
self.assertTrue(
|
||||||
|
Event.objects.filter(
|
||||||
|
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_group_create_members_empty(self):
|
||||||
|
"""Test group create"""
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data=dumps({"displayName": generate_id(), "externalId": ext_id, "members": []}),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
self.assertTrue(SCIMSourceGroup.objects.filter(source=self.source, id=ext_id).exists())
|
||||||
|
self.assertTrue(
|
||||||
|
Event.objects.filter(
|
||||||
|
action=EventAction.MODEL_CREATED, user__username=self.source.token.user.username
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_group_create_duplicate(self):
|
||||||
|
"""Test group create (duplicate)"""
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
existing = SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data=dumps(
|
||||||
|
{"displayName": generate_id(), "externalId": ext_id, "id": str(existing.group.pk)}
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 409)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
response.content,
|
||||||
|
{
|
||||||
|
"detail": "Group with ID exists already.",
|
||||||
|
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||||
|
"scimType": "uniqueness",
|
||||||
|
"status": 409,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_group_update(self):
|
||||||
|
"""Test group update"""
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
existing = SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.put(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||||
|
),
|
||||||
|
data=dumps(
|
||||||
|
{"displayName": generate_id(), "externalId": ext_id, "id": str(existing.pk)}
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, second=200)
|
||||||
|
|
||||||
|
def test_group_update_non_existent(self):
|
||||||
|
"""Test group update"""
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.put(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
"group_id": str(uuid4()),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data=dumps({"displayName": generate_id(), "externalId": ext_id, "id": ""}),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, second=404)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
response.content,
|
||||||
|
{
|
||||||
|
"detail": "Group not found.",
|
||||||
|
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||||
|
"status": 404,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_group_patch_add(self):
|
||||||
|
"""Test group patch"""
|
||||||
|
user = create_test_user()
|
||||||
|
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||||
|
),
|
||||||
|
data=dumps(
|
||||||
|
{
|
||||||
|
"Operations": [
|
||||||
|
{
|
||||||
|
"op": "Add",
|
||||||
|
"path": "members",
|
||||||
|
"value": {"value": str(user.uuid)},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, second=200)
|
||||||
|
self.assertTrue(group.users.filter(pk=user.pk).exists())
|
||||||
|
|
||||||
|
def test_group_patch_remove(self):
|
||||||
|
"""Test group patch"""
|
||||||
|
user = create_test_user()
|
||||||
|
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
group.users.add(user)
|
||||||
|
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||||
|
),
|
||||||
|
data=dumps(
|
||||||
|
{
|
||||||
|
"Operations": [
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "members",
|
||||||
|
"value": {"value": str(user.uuid)},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, second=200)
|
||||||
|
self.assertFalse(group.users.filter(pk=user.pk).exists())
|
||||||
|
|
||||||
|
def test_group_delete(self):
|
||||||
|
"""Test group delete"""
|
||||||
|
group = Group.objects.create(name=generate_id())
|
||||||
|
SCIMSourceGroup.objects.create(source=self.source, group=group, id=uuid4())
|
||||||
|
response = self.client.delete(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-groups",
|
||||||
|
kwargs={"source_slug": self.source.slug, "group_id": group.pk},
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, second=204)
|
@ -177,3 +177,51 @@ class TestSCIMUsers(APITestCase):
|
|||||||
SCIMSourceUser.objects.get(source=self.source, id=ext_id).user.attributes["phone"],
|
SCIMSourceUser.objects.get(source=self.source, id=ext_id).user.attributes["phone"],
|
||||||
"0123456789",
|
"0123456789",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_user_update(self):
|
||||||
|
"""Test user update"""
|
||||||
|
user = create_test_user()
|
||||||
|
existing = SCIMSourceUser.objects.create(source=self.source, user=user, id=uuid4())
|
||||||
|
ext_id = generate_id()
|
||||||
|
response = self.client.put(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-users",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
"user_id": str(user.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data=dumps(
|
||||||
|
{
|
||||||
|
"id": str(existing.pk),
|
||||||
|
"userName": generate_id(),
|
||||||
|
"externalId": ext_id,
|
||||||
|
"emails": [
|
||||||
|
{
|
||||||
|
"primary": True,
|
||||||
|
"value": user.email,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_user_delete(self):
|
||||||
|
"""Test user delete"""
|
||||||
|
user = create_test_user()
|
||||||
|
SCIMSourceUser.objects.create(source=self.source, user=user, id=uuid4())
|
||||||
|
response = self.client.delete(
|
||||||
|
reverse(
|
||||||
|
"authentik_sources_scim:v2-users",
|
||||||
|
kwargs={
|
||||||
|
"source_slug": self.source.slug,
|
||||||
|
"user_id": str(user.uuid),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type=SCIM_CONTENT_TYPE,
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {self.source.token.key}",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 204)
|
||||||
|
@ -8,6 +8,7 @@ from rest_framework.authentication import BaseAuthentication, get_authorization_
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from authentik.core.middleware import CTX_AUTH_VIA
|
||||||
from authentik.core.models import Token, TokenIntents, User
|
from authentik.core.models import Token, TokenIntents, User
|
||||||
from authentik.sources.scim.models import SCIMSource
|
from authentik.sources.scim.models import SCIMSource
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class SCIMTokenAuth(BaseAuthentication):
|
|||||||
_username, _, password = b64decode(key.encode()).decode().partition(":")
|
_username, _, password = b64decode(key.encode()).decode().partition(":")
|
||||||
token = self.check_token(password, source_slug)
|
token = self.check_token(password, source_slug)
|
||||||
if token:
|
if token:
|
||||||
|
CTX_AUTH_VIA.set("scim_basic")
|
||||||
return (token.user, token)
|
return (token.user, token)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -52,4 +54,5 @@ class SCIMTokenAuth(BaseAuthentication):
|
|||||||
token = self.check_token(key, source_slug)
|
token = self.check_token(key, source_slug)
|
||||||
if not token:
|
if not token:
|
||||||
return None
|
return None
|
||||||
|
CTX_AUTH_VIA.set("scim_token")
|
||||||
return (token.user, token)
|
return (token.user, token)
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
"""SCIM Utils"""
|
"""SCIM Utils"""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.paginator import Page, Paginator
|
from django.core.paginator import Page, Paginator
|
||||||
from django.db.models import Q, QuerySet
|
from django.db.models import Q, QuerySet
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.urls import resolve
|
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.parsers import JSONParser
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
@ -46,7 +44,7 @@ class SCIMView(APIView):
|
|||||||
logger: BoundLogger
|
logger: BoundLogger
|
||||||
|
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
parser_classes = [SCIMParser]
|
parser_classes = [SCIMParser, JSONParser]
|
||||||
renderer_classes = [SCIMRenderer]
|
renderer_classes = [SCIMRenderer]
|
||||||
|
|
||||||
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
def setup(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
|
||||||
@ -56,28 +54,6 @@ class SCIMView(APIView):
|
|||||||
def get_authenticators(self):
|
def get_authenticators(self):
|
||||||
return [SCIMTokenAuth(self)]
|
return [SCIMTokenAuth(self)]
|
||||||
|
|
||||||
def patch_resolve_value(self, raw_value: dict) -> User | Group | None:
|
|
||||||
"""Attempt to resolve a raw `value` attribute of a patch operation into
|
|
||||||
a database model"""
|
|
||||||
model = User
|
|
||||||
query = {}
|
|
||||||
if "$ref" in raw_value:
|
|
||||||
url = urlparse(raw_value["$ref"])
|
|
||||||
if match := resolve(url.path):
|
|
||||||
if match.url_name == "v2-users":
|
|
||||||
model = User
|
|
||||||
query = {"pk": int(match.kwargs["user_id"])}
|
|
||||||
elif "type" in raw_value:
|
|
||||||
match raw_value["type"]:
|
|
||||||
case "User":
|
|
||||||
model = User
|
|
||||||
query = {"pk": int(raw_value["value"])}
|
|
||||||
case "Group":
|
|
||||||
model = Group
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
return model.objects.filter(**query).first()
|
|
||||||
|
|
||||||
def filter_parse(self, request: Request):
|
def filter_parse(self, request: Request):
|
||||||
"""Parse the path of a Patch Operation"""
|
"""Parse the path of a Patch Operation"""
|
||||||
path = request.query_params.get("filter")
|
path = request.query_params.get("filter")
|
||||||
|
58
authentik/sources/scim/views/v2/exceptions.py
Normal file
58
authentik/sources/scim/views/v2/exceptions.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from pydanticscim.responses import SCIMError as BaseSCIMError
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMErrorTypes(Enum):
|
||||||
|
invalid_filter = "invalidFilter"
|
||||||
|
too_many = "tooMany"
|
||||||
|
uniqueness = "uniqueness"
|
||||||
|
mutability = "mutability"
|
||||||
|
invalid_syntax = "invalidSyntax"
|
||||||
|
invalid_path = "invalidPath"
|
||||||
|
no_target = "noTarget"
|
||||||
|
invalid_value = "invalidValue"
|
||||||
|
invalid_vers = "invalidVers"
|
||||||
|
sensitive = "sensitive"
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMError(BaseSCIMError):
|
||||||
|
scimType: SCIMErrorTypes | None = None
|
||||||
|
detail: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMValidationError(ValidationError):
|
||||||
|
status_code = 400
|
||||||
|
default_detail = SCIMError(scimType=SCIMErrorTypes.invalid_syntax, status=400)
|
||||||
|
|
||||||
|
def __init__(self, detail: SCIMError | None):
|
||||||
|
if detail is None:
|
||||||
|
detail = self.default_detail
|
||||||
|
detail.status = self.status_code
|
||||||
|
self.detail = detail.model_dump(mode="json", exclude_none=True)
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMConflictError(SCIMValidationError):
|
||||||
|
status_code = 409
|
||||||
|
|
||||||
|
def __init__(self, detail: str):
|
||||||
|
super().__init__(
|
||||||
|
SCIMError(
|
||||||
|
detail=detail,
|
||||||
|
scimType=SCIMErrorTypes.uniqueness,
|
||||||
|
status=self.status_code,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SCIMNotFoundError(SCIMValidationError):
|
||||||
|
status_code = 404
|
||||||
|
|
||||||
|
def __init__(self, detail: str):
|
||||||
|
super().__init__(
|
||||||
|
SCIMError(
|
||||||
|
detail=detail,
|
||||||
|
status=self.status_code,
|
||||||
|
)
|
||||||
|
)
|
@ -4,19 +4,25 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.http import Http404, QueryDict
|
from django.http import QueryDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from pydantic import ValidationError as PydanticValidationError
|
from pydantic import ValidationError as PydanticValidationError
|
||||||
from pydanticscim.group import GroupMember
|
from pydanticscim.group import GroupMember
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
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 scim2_filter_parser.attr_paths import AttrPath
|
||||||
|
|
||||||
from authentik.core.models import Group, User
|
from authentik.core.models import Group, User
|
||||||
from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
|
from authentik.providers.scim.clients.schema import SCIM_GROUP_SCHEMA, PatchOp, PatchOperation
|
||||||
from authentik.providers.scim.clients.schema import Group as SCIMGroupModel
|
from authentik.providers.scim.clients.schema import Group as SCIMGroupModel
|
||||||
from authentik.sources.scim.models import SCIMSourceGroup
|
from authentik.sources.scim.models import SCIMSourceGroup
|
||||||
from authentik.sources.scim.views.v2.base import SCIMObjectView
|
from authentik.sources.scim.views.v2.base import SCIMObjectView
|
||||||
|
from authentik.sources.scim.views.v2.exceptions import (
|
||||||
|
SCIMConflictError,
|
||||||
|
SCIMNotFoundError,
|
||||||
|
SCIMValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GroupsView(SCIMObjectView):
|
class GroupsView(SCIMObjectView):
|
||||||
@ -27,7 +33,7 @@ class GroupsView(SCIMObjectView):
|
|||||||
def group_to_scim(self, scim_group: SCIMSourceGroup) -> dict:
|
def group_to_scim(self, scim_group: SCIMSourceGroup) -> dict:
|
||||||
"""Convert Group to SCIM data"""
|
"""Convert Group to SCIM data"""
|
||||||
payload = SCIMGroupModel(
|
payload = SCIMGroupModel(
|
||||||
schemas=[SCIM_USER_SCHEMA],
|
schemas=[SCIM_GROUP_SCHEMA],
|
||||||
id=str(scim_group.group.pk),
|
id=str(scim_group.group.pk),
|
||||||
externalId=scim_group.id,
|
externalId=scim_group.id,
|
||||||
displayName=scim_group.group.name,
|
displayName=scim_group.group.name,
|
||||||
@ -58,7 +64,7 @@ class GroupsView(SCIMObjectView):
|
|||||||
if group_id:
|
if group_id:
|
||||||
connection = base_query.filter(source=self.source, group__group_uuid=group_id).first()
|
connection = base_query.filter(source=self.source, group__group_uuid=group_id).first()
|
||||||
if not connection:
|
if not connection:
|
||||||
raise Http404
|
raise SCIMNotFoundError("Group not found.")
|
||||||
return Response(self.group_to_scim(connection))
|
return Response(self.group_to_scim(connection))
|
||||||
connections = (
|
connections = (
|
||||||
base_query.filter(source=self.source).order_by("pk").filter(self.filter_parse(request))
|
base_query.filter(source=self.source).order_by("pk").filter(self.filter_parse(request))
|
||||||
@ -119,7 +125,7 @@ class GroupsView(SCIMObjectView):
|
|||||||
).first()
|
).first()
|
||||||
if connection:
|
if connection:
|
||||||
self.logger.debug("Found existing group")
|
self.logger.debug("Found existing group")
|
||||||
return Response(status=409)
|
raise SCIMConflictError("Group with ID exists already.")
|
||||||
connection = self.update_group(None, request.data)
|
connection = self.update_group(None, request.data)
|
||||||
return Response(self.group_to_scim(connection), status=201)
|
return Response(self.group_to_scim(connection), status=201)
|
||||||
|
|
||||||
@ -129,10 +135,44 @@ class GroupsView(SCIMObjectView):
|
|||||||
source=self.source, group__group_uuid=group_id
|
source=self.source, group__group_uuid=group_id
|
||||||
).first()
|
).first()
|
||||||
if not connection:
|
if not connection:
|
||||||
raise Http404
|
raise SCIMNotFoundError("Group not found.")
|
||||||
connection = self.update_group(connection, request.data)
|
connection = self.update_group(connection, request.data)
|
||||||
return Response(self.group_to_scim(connection), status=200)
|
return Response(self.group_to_scim(connection), status=200)
|
||||||
|
|
||||||
|
@atomic
|
||||||
|
def patch(self, request: Request, group_id: str, **kwargs) -> Response:
|
||||||
|
"""Patch group handler"""
|
||||||
|
connection = SCIMSourceGroup.objects.filter(
|
||||||
|
source=self.source, group__group_uuid=group_id
|
||||||
|
).first()
|
||||||
|
if not connection:
|
||||||
|
raise SCIMNotFoundError("Group not found.")
|
||||||
|
|
||||||
|
for _op in request.data.get("Operations", []):
|
||||||
|
operation = PatchOperation.model_validate(_op)
|
||||||
|
if operation.op.lower() not in ["add", "remove", "replace"]:
|
||||||
|
raise SCIMValidationError()
|
||||||
|
attr_path = AttrPath(f'{operation.path} eq ""', {})
|
||||||
|
if attr_path.first_path == ("members", None, None):
|
||||||
|
# FIXME: this can probably be de-duplicated
|
||||||
|
if operation.op == PatchOp.add:
|
||||||
|
if not isinstance(operation.value, list):
|
||||||
|
operation.value = [operation.value]
|
||||||
|
query = Q()
|
||||||
|
for member in operation.value:
|
||||||
|
query |= Q(uuid=member["value"])
|
||||||
|
if query:
|
||||||
|
connection.group.users.add(*User.objects.filter(query))
|
||||||
|
elif operation.op == PatchOp.remove:
|
||||||
|
if not isinstance(operation.value, list):
|
||||||
|
operation.value = [operation.value]
|
||||||
|
query = Q()
|
||||||
|
for member in operation.value:
|
||||||
|
query |= Q(uuid=member["value"])
|
||||||
|
if query:
|
||||||
|
connection.group.users.remove(*User.objects.filter(query))
|
||||||
|
return Response(self.group_to_scim(connection), status=200)
|
||||||
|
|
||||||
@atomic
|
@atomic
|
||||||
def delete(self, request: Request, group_id: str, **kwargs) -> Response:
|
def delete(self, request: Request, group_id: str, **kwargs) -> Response:
|
||||||
"""Delete group handler"""
|
"""Delete group handler"""
|
||||||
@ -140,7 +180,7 @@ class GroupsView(SCIMObjectView):
|
|||||||
source=self.source, group__group_uuid=group_id
|
source=self.source, group__group_uuid=group_id
|
||||||
).first()
|
).first()
|
||||||
if not connection:
|
if not connection:
|
||||||
raise Http404
|
raise SCIMNotFoundError("Group not found.")
|
||||||
connection.group.delete()
|
connection.group.delete()
|
||||||
connection.delete()
|
connection.delete()
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"""SCIM Meta views"""
|
"""SCIM Meta views"""
|
||||||
|
|
||||||
from django.http import Http404
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
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 authentik.sources.scim.views.v2.base import SCIMView
|
from authentik.sources.scim.views.v2.base import SCIMView
|
||||||
|
from authentik.sources.scim.views.v2.exceptions import SCIMNotFoundError
|
||||||
|
|
||||||
|
|
||||||
class ResourceTypesView(SCIMView):
|
class ResourceTypesView(SCIMView):
|
||||||
@ -138,7 +138,7 @@ class ResourceTypesView(SCIMView):
|
|||||||
resource = [x for x in resource_types if x.get("id") == resource_type]
|
resource = [x for x in resource_types if x.get("id") == resource_type]
|
||||||
if resource:
|
if resource:
|
||||||
return Response(resource[0])
|
return Response(resource[0])
|
||||||
raise Http404
|
raise SCIMNotFoundError("Resource not found.")
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import Http404
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
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 authentik.sources.scim.views.v2.base import SCIMView
|
from authentik.sources.scim.views.v2.base import SCIMView
|
||||||
|
from authentik.sources.scim.views.v2.exceptions import SCIMNotFoundError
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
settings.BASE_DIR / "authentik" / "sources" / "scim" / "schemas" / "schema.json",
|
settings.BASE_DIR / "authentik" / "sources" / "scim" / "schemas" / "schema.json",
|
||||||
@ -44,7 +44,7 @@ class SchemaView(SCIMView):
|
|||||||
schema = [x for x in schemas if x.get("id") == schema_uri]
|
schema = [x for x in schemas if x.get("id") == schema_uri]
|
||||||
if schema:
|
if schema:
|
||||||
return Response(schema[0])
|
return Response(schema[0])
|
||||||
raise Http404
|
raise SCIMNotFoundError("Schema not found.")
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||||
|
@ -33,6 +33,8 @@ class ServiceProviderConfigView(SCIMView):
|
|||||||
{
|
{
|
||||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
||||||
"authenticationSchemes": auth_schemas,
|
"authenticationSchemes": auth_schemas,
|
||||||
|
# We only support patch for groups currently, so don't broadly advertise it.
|
||||||
|
# Implementations that require Group patch will use it regardless of this flag.
|
||||||
"patch": {"supported": False},
|
"patch": {"supported": False},
|
||||||
"bulk": {"supported": False, "maxOperations": 0, "maxPayloadSize": 0},
|
"bulk": {"supported": False, "maxOperations": 0, "maxPayloadSize": 0},
|
||||||
"filter": {
|
"filter": {
|
||||||
|
@ -4,7 +4,7 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.http import Http404, QueryDict
|
from django.http import QueryDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from pydanticscim.user import Email, EmailKind, Name
|
from pydanticscim.user import Email, EmailKind, Name
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
@ -16,6 +16,7 @@ from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
|
|||||||
from authentik.providers.scim.clients.schema import User as SCIMUserModel
|
from authentik.providers.scim.clients.schema import User as SCIMUserModel
|
||||||
from authentik.sources.scim.models import SCIMSourceUser
|
from authentik.sources.scim.models import SCIMSourceUser
|
||||||
from authentik.sources.scim.views.v2.base import SCIMObjectView
|
from authentik.sources.scim.views.v2.base import SCIMObjectView
|
||||||
|
from authentik.sources.scim.views.v2.exceptions import SCIMConflictError, SCIMNotFoundError
|
||||||
|
|
||||||
|
|
||||||
class UsersView(SCIMObjectView):
|
class UsersView(SCIMObjectView):
|
||||||
@ -69,7 +70,7 @@ class UsersView(SCIMObjectView):
|
|||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if not connection:
|
if not connection:
|
||||||
raise Http404
|
raise SCIMNotFoundError("User not found.")
|
||||||
return Response(self.user_to_scim(connection))
|
return Response(self.user_to_scim(connection))
|
||||||
connections = (
|
connections = (
|
||||||
SCIMSourceUser.objects.filter(source=self.source).select_related("user").order_by("pk")
|
SCIMSourceUser.objects.filter(source=self.source).select_related("user").order_by("pk")
|
||||||
@ -122,7 +123,7 @@ class UsersView(SCIMObjectView):
|
|||||||
).first()
|
).first()
|
||||||
if connection:
|
if connection:
|
||||||
self.logger.debug("Found existing user")
|
self.logger.debug("Found existing user")
|
||||||
return Response(status=409)
|
raise SCIMConflictError("Group with ID exists already.")
|
||||||
connection = self.update_user(None, request.data)
|
connection = self.update_user(None, request.data)
|
||||||
return Response(self.user_to_scim(connection), status=201)
|
return Response(self.user_to_scim(connection), status=201)
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ class UsersView(SCIMObjectView):
|
|||||||
"""Update user handler"""
|
"""Update user handler"""
|
||||||
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
|
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
|
||||||
if not connection:
|
if not connection:
|
||||||
raise Http404
|
raise SCIMNotFoundError("User not found.")
|
||||||
self.update_user(connection, request.data)
|
self.update_user(connection, request.data)
|
||||||
return Response(self.user_to_scim(connection), status=200)
|
return Response(self.user_to_scim(connection), status=200)
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ class UsersView(SCIMObjectView):
|
|||||||
"""Delete user handler"""
|
"""Delete user handler"""
|
||||||
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
|
connection = SCIMSourceUser.objects.filter(source=self.source, user__uuid=user_id).first()
|
||||||
if not connection:
|
if not connection:
|
||||||
raise Http404
|
raise SCIMNotFoundError("User not found.")
|
||||||
connection.user.delete()
|
connection.user.delete()
|
||||||
connection.delete()
|
connection.delete()
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
version: 1
|
version: 1
|
||||||
metadata:
|
metadata:
|
||||||
name: OIDC conformance testing
|
name: OpenID Conformance testing
|
||||||
|
labels:
|
||||||
|
blueprints.goauthentik.io/instantiate: "false"
|
||||||
entries:
|
entries:
|
||||||
- identifiers:
|
- identifiers:
|
||||||
managed: goauthentik.io/providers/oauth2/scope-address
|
managed: goauthentik.io/providers/oauth2/scope-address
|
||||||
@ -21,38 +23,72 @@ entries:
|
|||||||
attrs:
|
attrs:
|
||||||
name: "authentik default OAuth Mapping: OpenID 'phone'"
|
name: "authentik default OAuth Mapping: OpenID 'phone'"
|
||||||
scope_name: phone
|
scope_name: phone
|
||||||
description: "General phone Information"
|
description: "General phone information"
|
||||||
expression: |
|
expression: |
|
||||||
return {
|
return {
|
||||||
"phone_number": "+1234",
|
"phone_number": "+1234",
|
||||||
"phone_number_verified": True,
|
"phone_number_verified": True,
|
||||||
}
|
}
|
||||||
|
- identifiers:
|
||||||
|
managed: goauthentik.io/providers/oauth2/scope-profile-oidc-standard
|
||||||
|
model: authentik_providers_oauth2.scopemapping
|
||||||
|
attrs:
|
||||||
|
name: "OIDC conformance profile"
|
||||||
|
scope_name: profile
|
||||||
|
description: "General profile information"
|
||||||
|
expression: |
|
||||||
|
return {
|
||||||
|
# Because authentik only saves the user's full name, and has no concept of first and last names,
|
||||||
|
# the full name is used as given name.
|
||||||
|
# You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
|
||||||
|
"name": request.user.name,
|
||||||
|
"given_name": request.user.name,
|
||||||
|
"preferred_username": request.user.username,
|
||||||
|
"nickname": request.user.username,
|
||||||
|
"groups": [group.name for group in request.user.ak_groups.all()],
|
||||||
|
"website" : "foo",
|
||||||
|
"zoneinfo" : "foo",
|
||||||
|
"birthdate" : "2000",
|
||||||
|
"gender" : "foo",
|
||||||
|
"profile" : "foo",
|
||||||
|
"middle_name" : "foo",
|
||||||
|
"locale" : "foo",
|
||||||
|
"picture" : "foo",
|
||||||
|
"updated_at" : 1748557810,
|
||||||
|
"family_name" : "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
- model: authentik_providers_oauth2.oauth2provider
|
- model: authentik_providers_oauth2.oauth2provider
|
||||||
id: provider
|
id: oidc-conformance-1
|
||||||
identifiers:
|
identifiers:
|
||||||
name: provider
|
name: oidc-conformance-1
|
||||||
attrs:
|
attrs:
|
||||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||||
|
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||||
|
# Required as OIDC Conformance test requires issues to be the same across multiple clients
|
||||||
issuer_mode: global
|
issuer_mode: global
|
||||||
client_id: 4054d882aff59755f2f279968b97ce8806a926e1
|
client_id: 4054d882aff59755f2f279968b97ce8806a926e1
|
||||||
client_secret: 4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867
|
client_secret: 4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867
|
||||||
redirect_uris: |
|
redirect_uris:
|
||||||
https://localhost:8443/test/a/authentik/callback
|
- matching_mode: strict
|
||||||
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
url: https://localhost:8443/test/a/authentik/callback
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://host.docker.internal:8443/test/a/authentik/callback
|
||||||
property_mappings:
|
property_mappings:
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile-oidc-standard]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
|
||||||
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-offline_access]]
|
||||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||||
- model: authentik_core.application
|
- model: authentik_core.application
|
||||||
identifiers:
|
identifiers:
|
||||||
slug: conformance
|
slug: oidc-conformance-1
|
||||||
attrs:
|
attrs:
|
||||||
provider: !KeyOf provider
|
provider: !KeyOf oidc-conformance-1
|
||||||
name: Conformance
|
name: OIDC Conformance (1)
|
||||||
|
|
||||||
- model: authentik_providers_oauth2.oauth2provider
|
- model: authentik_providers_oauth2.oauth2provider
|
||||||
id: oidc-conformance-2
|
id: oidc-conformance-2
|
||||||
@ -60,22 +96,27 @@ entries:
|
|||||||
name: oidc-conformance-2
|
name: oidc-conformance-2
|
||||||
attrs:
|
attrs:
|
||||||
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
|
||||||
|
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||||
|
# Required as OIDC Conformance test requires issues to be the same across multiple clients
|
||||||
issuer_mode: global
|
issuer_mode: global
|
||||||
client_id: ad64aeaf1efe388ecf4d28fcc537e8de08bcae26
|
client_id: ad64aeaf1efe388ecf4d28fcc537e8de08bcae26
|
||||||
client_secret: ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789
|
client_secret: ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789
|
||||||
redirect_uris: |
|
redirect_uris:
|
||||||
https://localhost:8443/test/a/authentik/callback
|
- matching_mode: strict
|
||||||
https://localhost.emobix.co.uk:8443/test/a/authentik/callback
|
url: https://localhost:8443/test/a/authentik/callback
|
||||||
|
- matching_mode: strict
|
||||||
|
url: https://host.docker.internal:8443/test/a/authentik/callback
|
||||||
property_mappings:
|
property_mappings:
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-openid]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-email]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-profile-oidc-standard]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-address]]
|
||||||
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-phone]]
|
||||||
|
- !Find [authentik_providers_oauth2.scopemapping, [managed, goauthentik.io/providers/oauth2/scope-offline_access]]
|
||||||
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
|
||||||
- model: authentik_core.application
|
- model: authentik_core.application
|
||||||
identifiers:
|
identifiers:
|
||||||
slug: oidc-conformance-2
|
slug: oidc-conformance-2
|
||||||
attrs:
|
attrs:
|
||||||
provider: !KeyOf oidc-conformance-2
|
provider: !KeyOf oidc-conformance-2
|
||||||
name: OIDC Conformance
|
name: OIDC Conformance (2)
|
6
go.mod
6
go.mod
@ -6,7 +6,7 @@ require (
|
|||||||
beryju.io/ldap v0.1.0
|
beryju.io/ldap v0.1.0
|
||||||
github.com/avast/retry-go/v4 v4.6.1
|
github.com/avast/retry-go/v4 v4.6.1
|
||||||
github.com/coreos/go-oidc/v3 v3.14.1
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
github.com/getsentry/sentry-go v0.33.0
|
github.com/getsentry/sentry-go v0.34.0
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.11
|
github.com/go-ldap/ldap/v3 v3.4.11
|
||||||
github.com/go-openapi/runtime v0.28.0
|
github.com/go-openapi/runtime v0.28.0
|
||||||
@ -23,13 +23,13 @@ require (
|
|||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.8.1
|
github.com/pires/go-proxyproto v0.8.1
|
||||||
github.com/prometheus/client_golang v1.22.0
|
github.com/prometheus/client_golang v1.22.0
|
||||||
github.com/redis/go-redis/v9 v9.10.0
|
github.com/redis/go-redis/v9 v9.11.0
|
||||||
github.com/sethvargo/go-envconfig v1.3.0
|
github.com/sethvargo/go-envconfig v1.3.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2025062.4
|
goauthentik.io/api/v3 v3.2025062.5
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.15.0
|
golang.org/x/sync v0.15.0
|
||||||
|
12
go.sum
12
go.sum
@ -71,8 +71,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
|
github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4=
|
||||||
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
@ -251,8 +251,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
|||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||||
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
@ -298,8 +298,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
goauthentik.io/api/v3 v3.2025062.4 h1:HuyL12kKserXT2w+wCDUYNRSeyCCGX81wU9SRCPuxDo=
|
goauthentik.io/api/v3 v3.2025062.5 h1:+eQe3S+9WxrO0QczbSQUhtfnCB1w2rse5wmgMkcRUio=
|
||||||
goauthentik.io/api/v3 v3.2025062.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025062.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
@ -119,7 +119,7 @@ def run_migrations():
|
|||||||
check_args = ["", "check"]
|
check_args = ["", "check"]
|
||||||
for label in django_db_config(CONFIG).keys():
|
for label in django_db_config(CONFIG).keys():
|
||||||
check_args.append(f"--database={label}")
|
check_args.append(f"--database={label}")
|
||||||
if not CONFIG.get_bool("DEBUG"):
|
if not CONFIG.get_bool("debug"):
|
||||||
check_args.append("--deploy")
|
check_args.append("--deploy")
|
||||||
execute_from_command_line(check_args)
|
execute_from_command_line(check_args)
|
||||||
finally:
|
finally:
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-06-19 00:10+0000\n"
|
"POT-Creation-Date: 2025-06-25 00:10+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -109,10 +109,6 @@ msgstr ""
|
|||||||
msgid "User does not have access to application."
|
msgid "User does not have access to application."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/api/devices.py
|
|
||||||
msgid "Extra description not available"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentik/core/api/groups.py
|
#: authentik/core/api/groups.py
|
||||||
msgid "Cannot set group as parent of itself."
|
msgid "Cannot set group as parent of itself."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
Binary file not shown.
118
packages/eslint-config/package-lock.json
generated
118
packages/eslint-config/package-lock.json
generated
@ -576,17 +576,17 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
||||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
"integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/type-utils": "8.34.1",
|
"@typescript-eslint/type-utils": "8.35.0",
|
||||||
"@typescript-eslint/utils": "8.34.1",
|
"@typescript-eslint/utils": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -600,7 +600,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.34.1",
|
"@typescript-eslint/parser": "^8.35.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.9.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
@ -616,16 +616,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
|
||||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -641,14 +641,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
|
||||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
"integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
"@typescript-eslint/tsconfig-utils": "^8.35.0",
|
||||||
"@typescript-eslint/types": "^8.34.1",
|
"@typescript-eslint/types": "^8.35.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -663,14 +663,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
|
||||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
"integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
"@typescript-eslint/visitor-keys": "8.35.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -681,9 +681,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
|
||||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
"integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -698,14 +698,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
|
||||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
"integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||||
"@typescript-eslint/utils": "8.34.1",
|
"@typescript-eslint/utils": "8.35.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@ -722,9 +722,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
|
||||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
"integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -736,16 +736,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
|
||||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
"integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.34.1",
|
"@typescript-eslint/project-service": "8.35.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
"@typescript-eslint/tsconfig-utils": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -804,16 +804,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
|
||||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
"integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
"@typescript-eslint/typescript-estree": "8.35.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -828,13 +828,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
|
||||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
"integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -4065,15 +4065,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
|
||||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
"integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
"@typescript-eslint/eslint-plugin": "8.35.0",
|
||||||
"@typescript-eslint/parser": "8.34.1",
|
"@typescript-eslint/parser": "8.35.0",
|
||||||
"@typescript-eslint/utils": "8.34.1"
|
"@typescript-eslint/utils": "8.35.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
@ -59,7 +59,7 @@ dependencies = [
|
|||||||
"pyyaml==6.0.2",
|
"pyyaml==6.0.2",
|
||||||
"requests-oauthlib==2.0.0",
|
"requests-oauthlib==2.0.0",
|
||||||
"scim2-filter-parser==0.7.0",
|
"scim2-filter-parser==0.7.0",
|
||||||
"sentry-sdk==2.30.0",
|
"sentry-sdk==2.31.0",
|
||||||
"service-identity==24.2.0",
|
"service-identity==24.2.0",
|
||||||
"setproctitle==1.3.6",
|
"setproctitle==1.3.6",
|
||||||
"structlog==25.4.0",
|
"structlog==25.4.0",
|
||||||
|
@ -44010,7 +44010,7 @@ components:
|
|||||||
- name
|
- name
|
||||||
Device:
|
Device:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for Duo authenticator devices
|
description: Serializer for authenticator devices
|
||||||
properties:
|
properties:
|
||||||
verbose_name:
|
verbose_name:
|
||||||
type: string
|
type: string
|
||||||
@ -44049,11 +44049,18 @@ components:
|
|||||||
nullable: true
|
nullable: true
|
||||||
extra_description:
|
extra_description:
|
||||||
type: string
|
type: string
|
||||||
|
nullable: true
|
||||||
description: Get extra description
|
description: Get extra description
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
external_id:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Get external Device ID
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- confirmed
|
- confirmed
|
||||||
- created
|
- created
|
||||||
|
- external_id
|
||||||
- extra_description
|
- extra_description
|
||||||
- last_updated
|
- last_updated
|
||||||
- last_used
|
- last_used
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
# #Test files for OpenID Conformance testing.
|
|
||||||
|
|
||||||
These config files assume testing is being done using the [OpenID Conformance Suite
|
|
||||||
](https://openid.net/certification/about-conformance-suite/), locally.
|
|
||||||
|
|
||||||
See https://gitlab.com/openid/conformance-suite/-/wikis/Developers/Build-&-Run for running the conformance suite locally.
|
|
||||||
|
|
||||||
Requires docker containers to be able to access the host via `host.docker.internal` and an entry in the hosts file that maps `host.docker.internal` to localhost.
|
|
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"alias": "authentik",
|
|
||||||
"description": "authentik",
|
|
||||||
"server": {
|
|
||||||
"discoveryUrl": "http://host.docker.internal:9000/application/o/conformance/.well-known/openid-configuration"
|
|
||||||
},
|
|
||||||
"client": {
|
|
||||||
"client_id": "4054d882aff59755f2f279968b97ce8806a926e1",
|
|
||||||
"client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867"
|
|
||||||
},
|
|
||||||
"client_secret_post": {
|
|
||||||
"client_id": "4054d882aff59755f2f279968b97ce8806a926e1",
|
|
||||||
"client_secret": "4c7e4933009437fb486b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867"
|
|
||||||
},
|
|
||||||
"client2": {
|
|
||||||
"client_id": "ad64aeaf1efe388ecf4d28fcc537e8de08bcae26",
|
|
||||||
"client_secret": "ff2e34a5b04c99acaf7241e25a950e7f6134c86936923d8c698d8f38bd57647750d661069612c0ee55045e29fe06aa101804bdae38e8360647d595e771fea789"
|
|
||||||
},
|
|
||||||
"consent": {}
|
|
||||||
}
|
|
29
tests/openid_conformance/compose.yml
Normal file
29
tests/openid_conformance/compose.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
mongodb:
|
||||||
|
image: mongo:6.0.13
|
||||||
|
httpd:
|
||||||
|
image: ghcr.io/beryju/oidc-conformance-suite-httpd:v5.1.32
|
||||||
|
ports:
|
||||||
|
- "8443:8443"
|
||||||
|
- "8444:8444"
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
server:
|
||||||
|
image: ghcr.io/beryju/oidc-conformance-suite-server:v5.1.32
|
||||||
|
ports:
|
||||||
|
- "9999:9999"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
command: >
|
||||||
|
java
|
||||||
|
-Xdebug -Xrunjdwp:transport=dt_socket,address=*:9999,server=y,suspend=n
|
||||||
|
-jar /server/fapi-test-suite.jar
|
||||||
|
-Djdk.tls.maxHandshakeMessageSize=65536
|
||||||
|
--fintechlabs.base_url=https://host.docker.internal:8443
|
||||||
|
--fintechlabs.base_mtls_url=https://host.docker.internal:8444
|
||||||
|
--fintechlabs.devmode=true
|
||||||
|
--fintechlabs.startredir=true
|
||||||
|
links:
|
||||||
|
- mongodb:mongodb
|
||||||
|
depends_on:
|
||||||
|
- mongodb
|
8
uv.lock
generated
8
uv.lock
generated
@ -317,7 +317,7 @@ requires-dist = [
|
|||||||
{ name = "pyyaml", specifier = "==6.0.2" },
|
{ name = "pyyaml", specifier = "==6.0.2" },
|
||||||
{ name = "requests-oauthlib", specifier = "==2.0.0" },
|
{ name = "requests-oauthlib", specifier = "==2.0.0" },
|
||||||
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
|
{ name = "scim2-filter-parser", specifier = "==0.7.0" },
|
||||||
{ name = "sentry-sdk", specifier = "==2.30.0" },
|
{ name = "sentry-sdk", specifier = "==2.31.0" },
|
||||||
{ name = "service-identity", specifier = "==24.2.0" },
|
{ name = "service-identity", specifier = "==24.2.0" },
|
||||||
{ name = "setproctitle", specifier = "==1.3.6" },
|
{ name = "setproctitle", specifier = "==1.3.6" },
|
||||||
{ name = "structlog", specifier = "==25.4.0" },
|
{ name = "structlog", specifier = "==25.4.0" },
|
||||||
@ -2945,15 +2945,15 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sentry-sdk"
|
name = "sentry-sdk"
|
||||||
version = "2.30.0"
|
version = "2.31.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "certifi" },
|
{ name = "certifi" },
|
||||||
{ name = "urllib3" },
|
{ name = "urllib3" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/04/4c/af31e0201b48469786ddeb1bf6fd3dfa3a291cc613a0fe6a60163a7535f9/sentry_sdk-2.30.0.tar.gz", hash = "sha256:436369b02afef7430efb10300a344fb61a11fe6db41c2b11f41ee037d2dd7f45", size = 326767, upload-time = "2025-06-12T10:34:34.733Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d0/45/c7ef7e12d8434fda8b61cdab432d8af64fb832480c93cdaf4bdcab7f5597/sentry_sdk-2.31.0.tar.gz", hash = "sha256:fed6d847f15105849cdf5dfdc64dcec356f936d41abb8c9d66adae45e60959ec", size = 334167, upload-time = "2025-06-24T16:36:26.066Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/99/31ac6faaae33ea698086692638f58d14f121162a8db0039e68e94135e7f1/sentry_sdk-2.30.0-py2.py3-none-any.whl", hash = "sha256:59391db1550662f746ea09b483806a631c3ae38d6340804a1a4c0605044f6877", size = 343149, upload-time = "2025-06-12T10:34:32.896Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/a2/9b6d8cc59f03251c583b3fec9d2f075dc09c0f6e030e0e0a3b223c6e64b2/sentry_sdk-2.31.0-py2.py3-none-any.whl", hash = "sha256:e953f5ab083e6599bab255b75d6829b33b3ddf9931a27ca00b4ab0081287e84f", size = 355638, upload-time = "2025-06-24T16:36:24.306Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
188
web/package-lock.json
generated
188
web/package-lock.json
generated
@ -22,7 +22,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.7.11",
|
"@formatjs/intl-listformat": "^7.7.11",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@goauthentik/api": "^2025.6.2-1750636159",
|
"@goauthentik/api": "^2025.6.2-1750801939",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
@ -34,7 +34,7 @@
|
|||||||
"@openlayers-elements/maps": "^0.4.0",
|
"@openlayers-elements/maps": "^0.4.0",
|
||||||
"@patternfly/elements": "^4.1.0",
|
"@patternfly/elements": "^4.1.0",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@sentry/browser": "^9.30.0",
|
"@sentry/browser": "^9.31.0",
|
||||||
"@spotlightjs/spotlight": "^3.0.1",
|
"@spotlightjs/spotlight": "^3.0.1",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@ -126,7 +126,7 @@
|
|||||||
"storybook-addon-mock": "^5.0.0",
|
"storybook-addon-mock": "^5.0.0",
|
||||||
"turnstile-types": "^1.2.3",
|
"turnstile-types": "^1.2.3",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.34.1",
|
"typescript-eslint": "^8.35.0",
|
||||||
"vite-plugin-lit-css": "^2.0.0",
|
"vite-plugin-lit-css": "^2.0.0",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
"vite-tsconfig-paths": "^5.0.1",
|
||||||
"wireit": "^0.14.12"
|
"wireit": "^0.14.12"
|
||||||
@ -1731,9 +1731,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@goauthentik/api": {
|
"node_modules/@goauthentik/api": {
|
||||||
"version": "2025.6.2-1750636159",
|
"version": "2025.6.2-1750801939",
|
||||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750636159.tgz",
|
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750801939.tgz",
|
||||||
"integrity": "sha512-LPseyRzzi5Wk/cP8suRYUhwe/sGdIsGIcaXUkl13jprkJCUXEfuLcfAgdJka2MnIPaMyBDv7oYxJ0IhV/sidEg=="
|
"integrity": "sha512-3s0pE6enhLEWVMD+zClORktBhUAw1vO/lCG0ATqm6xqbTfqGxPYWj5XMzYuX7+a2axxn1BFE134afWmdzDhThw=="
|
||||||
},
|
},
|
||||||
"node_modules/@goauthentik/core": {
|
"node_modules/@goauthentik/core": {
|
||||||
"resolved": "packages/core",
|
"resolved": "packages/core",
|
||||||
@ -4561,75 +4561,75 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/browser-utils": {
|
"node_modules/@sentry-internal/browser-utils": {
|
||||||
"version": "9.30.0",
|
"version": "9.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.31.0.tgz",
|
||||||
"integrity": "sha512-e6ZlN8oWheCB0YJSGlBNUlh6UPnY5Ecj1P+/cgeKBhNm7c3bIx4J50485hB8LQsu+b7Q11L2o/wucZ//Pb6FCg==",
|
"integrity": "sha512-rviu/jUmeQbY4rSO8l4pubOtRIhFtH5Gu/ryRNMTlpJRdomp4uxddqthHUDH5g6xCXZsMTyJEIdx0aTqbgr/GQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "9.30.0"
|
"@sentry/core": "9.31.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/feedback": {
|
"node_modules/@sentry-internal/feedback": {
|
||||||
"version": "9.30.0",
|
"version": "9.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.31.0.tgz",
|
||||||
"integrity": "sha512-qAZ7xxLqZM7GlEvmSUmTHnoueg+fc7esMQD4vH8pS7HI3n9C5MjGn3HHlndRpD8lL7iUUQ0TPZQgU6McbzMDyw==",
|
"integrity": "sha512-Ygi/8UZ7p2B4DhXQjZDtOc45vNUHkfk2XETBTBGkByEQkE8vygzSiKhgRcnVpzwq+8xKFMRy+PxvpcCo+PNQew==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/core": "9.30.0"
|
"@sentry/core": "9.31.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay": {
|
"node_modules/@sentry-internal/replay": {
|
||||||
"version": "9.30.0",
|
"version": "9.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.31.0.tgz",
|
||||||
"integrity": "sha512-+6wkqQGLJuFUzvGRzbh3iIhFGyxQx/Oxc0ODDKmz9ag2xYRjCYb3UUQXmQX9navAF0HXUsq8ajoJPm2L1ZyWVg==",
|
"integrity": "sha512-V5rvcO/xSj8JMw4ZnZT2cBYC+UOuIiZ2Flj4EoIurxMrTgowE1uMXUBA32EBfuB5/vQSJXB6W5uAudhk7LjBPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "9.30.0",
|
"@sentry-internal/browser-utils": "9.31.0",
|
||||||
"@sentry/core": "9.30.0"
|
"@sentry/core": "9.31.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry-internal/replay-canvas": {
|
"node_modules/@sentry-internal/replay-canvas": {
|
||||||
"version": "9.30.0",
|
"version": "9.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.31.0.tgz",
|
||||||
"integrity": "sha512-I4MxS27rfV7vnOU29L80y4baZ4I1XqpnYvC/yLN7C17nA8eDCufQ8WVomli41y8JETnfcxlm68z7CS0sO4RCSA==",
|
"integrity": "sha512-VGqfvQCIuXQZeecrBf8bd4sj8lYGzUA/2CffTAkad1nB1Onyz0Kzo54qLWemivCxA3ufHf6DCpNA3Loa/0ywFQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/replay": "9.30.0",
|
"@sentry-internal/replay": "9.31.0",
|
||||||
"@sentry/core": "9.30.0"
|
"@sentry/core": "9.31.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/browser": {
|
"node_modules/@sentry/browser": {
|
||||||
"version": "9.30.0",
|
"version": "9.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.31.0.tgz",
|
||||||
"integrity": "sha512-sRyW6A9nIieTTI26MYXk1DmWEhmphTjZevusNWla+vvUigCmSjuH+xZw19w43OyvF3bu261Skypnm/mAalOTwg==",
|
"integrity": "sha512-DzG72JJTqHzE0Qo2fHeHm3xgFs97InaSQStmTMxOA59yPqvAXbweNPcsgCNu1q76+jZyaJcoy1qOwahnLuEVDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry-internal/browser-utils": "9.30.0",
|
"@sentry-internal/browser-utils": "9.31.0",
|
||||||
"@sentry-internal/feedback": "9.30.0",
|
"@sentry-internal/feedback": "9.31.0",
|
||||||
"@sentry-internal/replay": "9.30.0",
|
"@sentry-internal/replay": "9.31.0",
|
||||||
"@sentry-internal/replay-canvas": "9.30.0",
|
"@sentry-internal/replay-canvas": "9.31.0",
|
||||||
"@sentry/core": "9.30.0"
|
"@sentry/core": "9.31.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/core": {
|
"node_modules/@sentry/core": {
|
||||||
"version": "9.30.0",
|
"version": "9.31.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.31.0.tgz",
|
||||||
"integrity": "sha512-JfEpeQ8a1qVJEb9DxpFTFy1J1gkNdlgKAPiqYGNnm4yQbnfl2Kb/iEo1if70FkiHc52H8fJwISEF90pzMm6lPg==",
|
"integrity": "sha512-6JeoPGvBgT9m2YFIf2CrW+KrrOYzUqb9+Xwr/Dw25kPjVKy+WJjWqK8DKCNLgkBA22OCmSOmHuRwFR0YxGVdZQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@ -7415,17 +7415,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
||||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
"integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/type-utils": "8.34.1",
|
"@typescript-eslint/type-utils": "8.35.0",
|
||||||
"@typescript-eslint/utils": "8.34.1",
|
"@typescript-eslint/utils": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -7439,7 +7439,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.34.1",
|
"@typescript-eslint/parser": "^8.35.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.9.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
@ -7455,16 +7455,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
|
||||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7480,14 +7480,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
|
||||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
"integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
"@typescript-eslint/tsconfig-utils": "^8.35.0",
|
||||||
"@typescript-eslint/types": "^8.34.1",
|
"@typescript-eslint/types": "^8.35.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7502,14 +7502,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
|
||||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
"integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
"@typescript-eslint/visitor-keys": "8.35.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -7520,9 +7520,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
|
||||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
"integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7537,14 +7537,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
|
||||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
"integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||||
"@typescript-eslint/utils": "8.34.1",
|
"@typescript-eslint/utils": "8.35.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.1.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
@ -7561,9 +7561,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
|
||||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
"integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7575,16 +7575,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
|
||||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
"integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.34.1",
|
"@typescript-eslint/project-service": "8.35.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
"@typescript-eslint/tsconfig-utils": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -7604,16 +7604,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
|
||||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
"integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.35.0",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
"@typescript-eslint/typescript-estree": "8.35.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -7628,13 +7628,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
|
||||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
"integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.35.0",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -27217,15 +27217,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.34.1",
|
"version": "8.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
|
||||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
"integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
"@typescript-eslint/eslint-plugin": "8.35.0",
|
||||||
"@typescript-eslint/parser": "8.34.1",
|
"@typescript-eslint/parser": "8.35.0",
|
||||||
"@typescript-eslint/utils": "8.34.1"
|
"@typescript-eslint/utils": "8.35.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.7.11",
|
"@formatjs/intl-listformat": "^7.7.11",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@goauthentik/api": "^2025.6.2-1750636159",
|
"@goauthentik/api": "^2025.6.2-1750801939",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"@openlayers-elements/maps": "^0.4.0",
|
"@openlayers-elements/maps": "^0.4.0",
|
||||||
"@patternfly/elements": "^4.1.0",
|
"@patternfly/elements": "^4.1.0",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@sentry/browser": "^9.30.0",
|
"@sentry/browser": "^9.31.0",
|
||||||
"@spotlightjs/spotlight": "^3.0.1",
|
"@spotlightjs/spotlight": "^3.0.1",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@ -197,7 +197,7 @@
|
|||||||
"storybook-addon-mock": "^5.0.0",
|
"storybook-addon-mock": "^5.0.0",
|
||||||
"turnstile-types": "^1.2.3",
|
"turnstile-types": "^1.2.3",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.34.1",
|
"typescript-eslint": "^8.35.0",
|
||||||
"vite-plugin-lit-css": "^2.0.0",
|
"vite-plugin-lit-css": "^2.0.0",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
"vite-tsconfig-paths": "^5.0.1",
|
||||||
"wireit": "^0.14.12"
|
"wireit": "^0.14.12"
|
||||||
|
@ -64,7 +64,7 @@ export const EntryPoint = /** @type {const} */ ({
|
|||||||
in: resolve(PackageRoot, "src", "flow", "index.entrypoint.ts"),
|
in: resolve(PackageRoot, "src", "flow", "index.entrypoint.ts"),
|
||||||
out: resolve(DistDirectory, "flow", "FlowInterface"),
|
out: resolve(DistDirectory, "flow", "FlowInterface"),
|
||||||
},
|
},
|
||||||
Standalone: {
|
StandaloneAPI: {
|
||||||
in: resolve(PackageRoot, "src", "standalone", "api-browser/index.entrypoint.ts"),
|
in: resolve(PackageRoot, "src", "standalone", "api-browser/index.entrypoint.ts"),
|
||||||
out: resolve(DistDirectory, "standalone", "api-browser", "index"),
|
out: resolve(DistDirectory, "standalone", "api-browser", "index"),
|
||||||
},
|
},
|
||||||
|
@ -64,7 +64,7 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
quickActions: QuickAction[] = [
|
quickActions: QuickAction[] = [
|
||||||
[msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
|
[msg("Create a new application"), paramURL("/core/applications", { createWizard: true })],
|
||||||
[msg("Check the logs"), paramURL("/events/log")],
|
[msg("Check the logs"), paramURL("/events/log")],
|
||||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
||||||
[msg("Manage users"), paramURL("/identity/users")],
|
[msg("Manage users"), paramURL("/identity/users")],
|
||||||
|
@ -89,7 +89,7 @@ export class RecentEventsCard extends Table<Event> {
|
|||||||
|
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state
|
||||||
><span slot="header">${msg("No Events found.")}</span>
|
><span>${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -112,7 +112,7 @@ export class ApplicationViewPage extends AKElement {
|
|||||||
|
|
||||||
renderApp(): TemplateResult {
|
renderApp(): TemplateResult {
|
||||||
if (!this.application) {
|
if (!this.application) {
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html`<ak-tabs>
|
return html`<ak-tabs>
|
||||||
${this.missingOutpost
|
${this.missingOutpost
|
||||||
|
@ -118,13 +118,12 @@ export class ApplicationEntitlementsPage extends Table<ApplicationEntitlement> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state icon="pf-icon-module"
|
||||||
header=${msg("No app entitlements created.")}
|
><span>${msg("No app entitlements created.")}</span>
|
||||||
icon="pf-icon-module"
|
|
||||||
>
|
|
||||||
<div slot="body">
|
<div slot="body">
|
||||||
${msg(
|
${msg(
|
||||||
"This application does currently not have any application entitlement defined.",
|
"This application does currently not have any application entitlements defined.",
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div slot="primary"></div>
|
<div slot="primary"></div>
|
||||||
|
@ -116,7 +116,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
|||||||
.content=${[]}
|
.content=${[]}
|
||||||
></ak-select-table>
|
></ak-select-table>
|
||||||
<ak-empty-state icon="pf-icon-module"
|
<ak-empty-state icon="pf-icon-module"
|
||||||
><span slot="header">${msg("No bound policies.")} </span>
|
><span>${msg("No bound policies.")}</span>
|
||||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<button
|
<button
|
||||||
|
@ -83,7 +83,7 @@ export class ApplicationWizardProviderChoiceStep extends WithLicenseSummary(Appl
|
|||||||
}}
|
}}
|
||||||
></ak-wizard-page-type-create>
|
></ak-wizard-page-type-create>
|
||||||
</form>`
|
</form>`
|
||||||
: html`<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>`;
|
: html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +109,8 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
|||||||
return super.renderEmpty(html`
|
return super.renderEmpty(html`
|
||||||
${inner
|
${inner
|
||||||
? inner
|
? inner
|
||||||
: html`<ak-empty-state
|
: html`<ak-empty-state icon=${this.pageIcon()}
|
||||||
icon=${this.pageIcon()}
|
><span>${msg("No licenses found.")}</span>
|
||||||
header="${msg("No licenses found.")}"
|
|
||||||
>
|
|
||||||
<div slot="body">
|
<div slot="body">
|
||||||
${this.searchEnabled() ? this.renderEmptyClearSearch() : html``}
|
${this.searchEnabled() ? this.renderEmptyClearSearch() : html``}
|
||||||
</div>
|
</div>
|
||||||
|
@ -136,7 +136,7 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
|||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state icon="pf-icon-module">
|
html`<ak-empty-state icon="pf-icon-module">
|
||||||
<span slot="header">${msg("No Stages bound")}</span>
|
<span>${msg("No Stages bound")}</span>
|
||||||
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-stage-wizard
|
<ak-stage-wizard
|
||||||
|
@ -199,7 +199,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state icon="pf-icon-module"
|
html`<ak-empty-state icon="pf-icon-module"
|
||||||
><span slot="header">${msg("No Policies bound.")}</span>
|
><span>${msg("No Policies bound.")}</span>
|
||||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||||
<div slot="primary">
|
<div slot="primary">
|
||||||
<ak-policy-wizard
|
<ak-policy-wizard
|
||||||
|
@ -42,7 +42,7 @@ export class ProviderViewPage extends AKElement {
|
|||||||
|
|
||||||
renderProvider(): TemplateResult {
|
renderProvider(): TemplateResult {
|
||||||
if (!this.provider) {
|
if (!this.provider) {
|
||||||
return html`<ak-empty-state loading fullHeight></ak-empty-state>`;
|
return html`<ak-empty-state loading full-height></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.provider?.component) {
|
switch (this.provider?.component) {
|
||||||
case "ak-provider-saml-form":
|
case "ak-provider-saml-form":
|
||||||
|
@ -34,7 +34,7 @@ export class SourceViewPage extends AKElement {
|
|||||||
|
|
||||||
renderSource(): TemplateResult {
|
renderSource(): TemplateResult {
|
||||||
if (!this.source) {
|
if (!this.source) {
|
||||||
return html`<ak-empty-state loading fullHeight></ak-empty-state>`;
|
return html`<ak-empty-state loading full-height></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.source?.component) {
|
switch (this.source?.component) {
|
||||||
case "ak-source-kerberos-form":
|
case "ak-source-kerberos-form":
|
||||||
|
@ -7,7 +7,7 @@ import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
|||||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import { AuthenticatorsApi, Device } from "@goauthentik/api";
|
import { AuthenticatorsApi, Device } from "@goauthentik/api";
|
||||||
@ -104,8 +104,11 @@ export class UserDeviceTable extends Table<Device> {
|
|||||||
row(item: Device): TemplateResult[] {
|
row(item: Device): TemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`${item.name}`,
|
html`${item.name}`,
|
||||||
html`${deviceTypeName(item)}
|
html`<div>
|
||||||
${item.extraDescription ? ` - ${item.extraDescription}` : ""}`,
|
${deviceTypeName(item)}
|
||||||
|
${item.extraDescription ? ` - ${item.extraDescription}` : ""}
|
||||||
|
</div>
|
||||||
|
${item.externalId ? html` <small>${item.externalId}</small> ` : nothing} `,
|
||||||
html`${item.confirmed ? msg("Yes") : msg("No")}`,
|
html`${item.confirmed ? msg("Yes") : msg("No")}`,
|
||||||
html`${item.created.getTime() > 0
|
html`${item.created.getTime() > 0
|
||||||
? html`<div>${formatElapsedTime(item.created)}</div>
|
? html`<div>${formatElapsedTime(item.created)}</div>
|
||||||
|
@ -133,7 +133,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
|
|||||||
async apiEndpoint(): Promise<PaginatedResponse<User>> {
|
async apiEndpoint(): Promise<PaginatedResponse<User>> {
|
||||||
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
||||||
...(await this.defaultEndpointConfig()),
|
...(await this.defaultEndpointConfig()),
|
||||||
pathStartswith: getURLParam("path", ""),
|
pathStartswith: this.activePath,
|
||||||
isActive: this.hideDeactivated ? true : undefined,
|
isActive: this.hideDeactivated ? true : undefined,
|
||||||
includeGroups: false,
|
includeGroups: false,
|
||||||
});
|
});
|
||||||
|
@ -94,7 +94,7 @@ export class ObjectChangelog extends Table<Event> {
|
|||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state
|
||||||
><span slot="header">${msg("No Events found.")}</span>
|
><span>${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -67,7 +67,7 @@ export class UserEvents extends Table<Event> {
|
|||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state
|
||||||
><span slot="header">${msg("No Events found.")}</span>
|
><span>${msg("No Events found.")}</span>
|
||||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -148,5 +148,31 @@ export class AKElement extends LitElement implements AKElementProps {
|
|||||||
return this.#styleRoot;
|
return this.#styleRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected hasSlotted(name: string | null) {
|
||||||
|
const isNotNestedSlot = (start: Element) => {
|
||||||
|
let node = start.parentNode;
|
||||||
|
while (node && node !== this) {
|
||||||
|
if (node instanceof Element && node.hasAttribute("slot")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// All child slots accessible from the component's LightDOM that match the request
|
||||||
|
const allChildSlotRequests =
|
||||||
|
typeof name === "string"
|
||||||
|
? [...this.querySelectorAll(`[slot="${name}"]`)]
|
||||||
|
: [...this.children].filter((child) => {
|
||||||
|
const slotAttr = child.getAttribute("slot");
|
||||||
|
return !slotAttr || slotAttr === "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// All child slots accessible from the LightDom that match the request *and* are not nested
|
||||||
|
// within another slotted element.
|
||||||
|
return allChildSlotRequests.filter((node) => isNotNestedSlot(node)).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
@ -3,38 +3,63 @@ import { AKElement } from "@goauthentik/elements/Base";
|
|||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
import { type SlottedTemplateResult, type Spread } from "@goauthentik/elements/types";
|
||||||
import { spread } from "@open-wc/lit-helpers";
|
import { spread } from "@open-wc/lit-helpers";
|
||||||
import { SlotController } from "@patternfly/pfe-core/controllers/slot-controller.js";
|
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { css, html, nothing } from "lit";
|
import { css, html, nothing, render } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
|
|
||||||
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
||||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the EmptyState component
|
||||||
|
*/
|
||||||
export interface IEmptyState {
|
export interface IEmptyState {
|
||||||
|
/** Font Awesome icon class (e.g., "fa-user", "fa-folder") to display */
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
/** When true, will automatically show the loading spinner. Overrides `icon`. */
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, will automatically fill the header with the "Loading" message and show the loading
|
||||||
|
* spinner. Overrides 'loading'.
|
||||||
|
*/
|
||||||
|
defaultLabel?: boolean;
|
||||||
|
|
||||||
|
/** Whether the empty state should take up the full height of its container */
|
||||||
fullHeight?: boolean;
|
fullHeight?: boolean;
|
||||||
header?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ak-empty-state
|
||||||
|
* @class EmptyState
|
||||||
|
*
|
||||||
|
* A component for displaying empty states with optional icons, headings, body text, and actions.
|
||||||
|
* Follows PatternFly design patterns for empty state presentations.
|
||||||
|
*
|
||||||
|
* ## Slots
|
||||||
|
*
|
||||||
|
* @slot - The main heading text for the empty state
|
||||||
|
* @slot body - Descriptive text explaining the empty state or what the user can do
|
||||||
|
* @slot primary - Primary action buttons or other interactive elements
|
||||||
|
*
|
||||||
|
*/
|
||||||
@customElement("ak-empty-state")
|
@customElement("ak-empty-state")
|
||||||
export class EmptyState extends AKElement implements IEmptyState {
|
export class EmptyState extends AKElement implements IEmptyState {
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
icon = "";
|
public icon = "";
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean, reflect: true })
|
||||||
loading = false;
|
public loading = false;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean, reflect: true, attribute: "default-label" })
|
||||||
fullHeight = false;
|
public defaultLabel = false;
|
||||||
|
|
||||||
@property()
|
@property({ type: Boolean, attribute: "full-height" })
|
||||||
header?: string;
|
public fullHeight = false;
|
||||||
|
|
||||||
slots = new SlotController(this, "header", "body", "primary");
|
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
@ -50,32 +75,49 @@ export class EmptyState extends AKElement implements IEmptyState {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
willUpdate() {
|
||||||
const showHeader = this.loading || this.slots.hasSlotted("header");
|
if (this.defaultLabel && this.querySelector("span:not([slot])") === null) {
|
||||||
const header = () =>
|
render(html`<span>${msg("Loading")}</span>`, this);
|
||||||
this.slots.hasSlotted("header")
|
}
|
||||||
? html`<slot name="header"></slot>`
|
}
|
||||||
: html`<span>${msg("Loading")}</span>`;
|
|
||||||
|
|
||||||
return html`<div class="pf-c-empty-state ${this.fullHeight && "pf-m-full-height"}">
|
get localAriaLabel() {
|
||||||
<div class="pf-c-empty-state__content">
|
const result = this.querySelector("span:not([slot])");
|
||||||
${this.loading
|
return result instanceof HTMLElement ? result.innerText || undefined : undefined;
|
||||||
? html`<div class="pf-c-empty-state__icon">
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const hasHeading = this.hasSlotted(null);
|
||||||
|
const loading = this.loading || this.defaultLabel;
|
||||||
|
const classes = {
|
||||||
|
"pf-c-empty-state": true,
|
||||||
|
"pf-m-full-height": this.fullHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`<div aria-label=${this.localAriaLabel ?? nothing} class="${classMap(classes)}">
|
||||||
|
<div class="pf-c-empty-state__content" role="progressbar">
|
||||||
|
${loading
|
||||||
|
? html`<div part="spinner" class="pf-c-empty-state__icon">
|
||||||
<ak-spinner size=${PFSize.XLarge}></ak-spinner>
|
<ak-spinner size=${PFSize.XLarge}></ak-spinner>
|
||||||
</div>`
|
</div>`
|
||||||
: html`<i
|
: html`<i
|
||||||
|
part="icon"
|
||||||
class="pf-icon fa ${this.icon ||
|
class="pf-icon fa ${this.icon ||
|
||||||
"fa-question-circle"} pf-c-empty-state__icon"
|
"fa-question-circle"} pf-c-empty-state__icon"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>`}
|
></i>`}
|
||||||
${showHeader ? html` <h1 class="pf-c-title pf-m-lg">${header()}</h1>` : nothing}
|
${hasHeading
|
||||||
${this.slots.hasSlotted("body")
|
? html` <h1 part="heading" class="pf-c-title pf-m-lg" id="empty-state-heading">
|
||||||
? html` <div class="pf-c-empty-state__body">
|
<slot></slot>
|
||||||
|
</h1>`
|
||||||
|
: nothing}
|
||||||
|
${this.hasSlotted("body")
|
||||||
|
? html` <div part="body" class="pf-c-empty-state__body">
|
||||||
<slot name="body"></slot>
|
<slot name="body"></slot>
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.slots.hasSlotted("primary")
|
${this.hasSlotted("primary")
|
||||||
? html` <div class="pf-c-empty-state__primary">
|
? html` <div part="primary" class="pf-c-empty-state__primary">
|
||||||
<slot name="primary"></slot>
|
<slot name="primary"></slot>
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
@ -84,10 +126,37 @@ export class EmptyState extends AKElement implements IEmptyState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function akEmptyState(properties: IEmptyState, content: SlottedTemplateResult = nothing) {
|
interface IEmptyStateContent {
|
||||||
const message =
|
heading?: SlottedTemplateResult;
|
||||||
typeof content === "string" ? html`<span slot="body">${content}</span>` : content;
|
body?: SlottedTemplateResult;
|
||||||
return html`<ak-empty-state ${spread(properties as Spread)}>${message}</ak-empty-state>`;
|
primary?: SlottedTemplateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentKey = keyof IEmptyStateContent;
|
||||||
|
type ContentValue = SlottedTemplateResult | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate `<ak-empty-state>` programmatically
|
||||||
|
*
|
||||||
|
* @param properties - properties to apply to the component.
|
||||||
|
* @param content - strings or TemplateResults for the slots in `<ak-empty-state>`
|
||||||
|
* @returns TemplateResult for the ak-empty-state element
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function akEmptyState(properties: IEmptyState = {}, content: IEmptyStateContent = {}) {
|
||||||
|
// `heading` here is an Object.key of ILoadingOverlayContent, not the obsolete
|
||||||
|
// slot-name.
|
||||||
|
const stringToSlot = (name: string, c: ContentValue) =>
|
||||||
|
name === "heading" ? html`<span>${c}</span>` : html`<span slot=${name}>${c}</span>`;
|
||||||
|
|
||||||
|
const stringToTemplate = (name: string, c: ContentValue) =>
|
||||||
|
typeof c === "string" ? stringToSlot(name, c) : c;
|
||||||
|
|
||||||
|
const items = Object.entries(content)
|
||||||
|
.map(([name, content]) => stringToTemplate(name, content))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return html`<ak-empty-state ${spread(properties as Spread)}>${items}</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -5,30 +5,59 @@ import { spread } from "@open-wc/lit-helpers";
|
|||||||
|
|
||||||
import { css, html, nothing } from "lit";
|
import { css, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
export interface ILoadingOverlay {
|
export interface ILoadingOverlay {
|
||||||
|
/**
|
||||||
|
* Whether this overlay should appear above all other overlays (z-index: 999)
|
||||||
|
*/
|
||||||
topmost?: boolean;
|
topmost?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the loading spinner animation
|
||||||
|
*/
|
||||||
|
noSpinner?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon name to display instead of the default loading spinner
|
||||||
|
*/
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ak-loading-overlay
|
||||||
|
* @class LoadingOverlay
|
||||||
|
*
|
||||||
|
* A component for for showing a loading message above a darkening background, in order
|
||||||
|
* to pause interaction while dynamically importing a major component.
|
||||||
|
*
|
||||||
|
* ## Slots
|
||||||
|
*
|
||||||
|
* @slot - The main heading text for the loading state
|
||||||
|
* @slot body - Descriptive text explaining the loading state
|
||||||
|
*
|
||||||
|
*/
|
||||||
@customElement("ak-loading-overlay")
|
@customElement("ak-loading-overlay")
|
||||||
export class LoadingOverlay extends AKElement implements ILoadingOverlay {
|
export class LoadingOverlay extends AKElement implements ILoadingOverlay {
|
||||||
// Do not camelize: https://www.merriam-webster.com/dictionary/topmost
|
// Do not camelize: https://www.merriam-webster.com/dictionary/topmost
|
||||||
@property({ type: Boolean, attribute: "topmost" })
|
@property({ type: Boolean, attribute: "topmost" })
|
||||||
topmost = false;
|
topmost = false;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean, attribute: "no-spinner" })
|
||||||
loading = true;
|
noSpinner = false;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
icon = "";
|
icon?: string;
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -46,20 +75,49 @@ export class LoadingOverlay extends AKElement implements ILoadingOverlay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<ak-empty-state ?loading=${this.loading} header="" icon=${this.icon}>
|
// Nested slots. Can get a little cognitively heavy, so be careful if you're editing here...
|
||||||
<span slot="body"><slot></slot></span>
|
return html`<ak-empty-state ?loading=${!this.noSpinner} icon=${ifDefined(this.icon)}>
|
||||||
|
${this.hasSlotted(null) ? html`<span><slot></slot></span>` : nothing}
|
||||||
|
${this.hasSlotted("body")
|
||||||
|
? html`<span slot="body"><slot name="body"></slot></span>`
|
||||||
|
: nothing}
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ILoadingOverlayContent {
|
||||||
|
heading?: SlottedTemplateResult;
|
||||||
|
body?: SlottedTemplateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentKey = keyof ILoadingOverlayContent;
|
||||||
|
type ContentValue = SlottedTemplateResult | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to create `<ak-loading-overlay>` programmatically
|
||||||
|
*
|
||||||
|
* @param properties - properties to apply to the component.
|
||||||
|
* @param content - strings or TemplateResults for the slots in `<ak-loading-overlay>`
|
||||||
|
* @returns TemplateResult for the ak-loading-overlay element
|
||||||
|
*
|
||||||
|
*/
|
||||||
export function akLoadingOverlay(
|
export function akLoadingOverlay(
|
||||||
properties: ILoadingOverlay,
|
properties: ILoadingOverlay = {},
|
||||||
content: SlottedTemplateResult = nothing,
|
content: ILoadingOverlayContent = {},
|
||||||
) {
|
) {
|
||||||
const message = typeof content === "string" ? html`<span>${content}</span>` : content;
|
// `heading` here is an Object.key of ILoadingOverlayContent, not the obsolete
|
||||||
return html`<ak-loading-overlay ${spread(properties as Spread)}
|
// slot-name.
|
||||||
>${message}</ak-loading-overlay
|
const stringToSlot = (name: string, c: ContentValue) =>
|
||||||
>`;
|
name === "heading" ? html`<span>${c}</span>` : html`<span slot=${name}>${c}</span>`;
|
||||||
|
|
||||||
|
const stringToTemplate = (name: string, c: ContentValue) =>
|
||||||
|
typeof c === "string" ? stringToSlot(name, c) : c;
|
||||||
|
|
||||||
|
const items = Object.entries(content)
|
||||||
|
.map(([name, content]) => stringToTemplate(name, content))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return html`<ak-loading-overlay ${spread(properties as Spread)}>${items}</ak-loading-overlay>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -32,8 +32,8 @@ import {
|
|||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
function localeComparator(a: DualSelectPair, b: DualSelectPair) {
|
function localeComparator(a: DualSelectPair, b: DualSelectPair) {
|
||||||
const aSortBy = a[2] || a[0];
|
const aSortBy = String(a[2] || a[0]);
|
||||||
const bSortBy = b[2] || b[0];
|
const bSortBy = String(b[2] || b[0]);
|
||||||
|
|
||||||
return aSortBy.localeCompare(bSortBy);
|
return aSortBy.localeCompare(bSortBy);
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ export abstract class AKChart<T> extends AKElement {
|
|||||||
${this.error
|
${this.error
|
||||||
? html`
|
? html`
|
||||||
<ak-empty-state icon="fa-times"
|
<ak-empty-state icon="fa-times"
|
||||||
><span slot="header">${msg("Failed to fetch data.")}</span>
|
><span>${msg("Failed to fetch data.")}</span>
|
||||||
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
<p slot="body">${pluckErrorDetail(this.error)}</p>
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
`
|
`
|
||||||
|
@ -40,9 +40,7 @@ export class LogViewer extends Table<LogEvent> {
|
|||||||
|
|
||||||
renderEmpty(): TemplateResult {
|
renderEmpty(): TemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state><span>${msg("No log messages.")}</span> </ak-empty-state>`,
|
||||||
><span slot="header">${msg("No log messages.")}</span>
|
|
||||||
</ak-empty-state>`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ export class NotificationDrawer extends AKElement {
|
|||||||
|
|
||||||
renderEmpty() {
|
renderEmpty() {
|
||||||
return html`<ak-empty-state
|
return html`<ak-empty-state
|
||||||
><span slot="header">${msg("No notifications found.")}</span>
|
><span>${msg("No notifications found.")}</span>
|
||||||
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
<div slot="body">${msg("You don't have any notifications currently.")}</div>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
|
|
||||||
|
|
||||||
import * as EmptyStateStories from "./EmptyState.stories";
|
|
||||||
|
|
||||||
<Meta of={EmptyStateStories} />
|
|
||||||
|
|
||||||
# EmptyState
|
|
||||||
|
|
||||||
The EmptyState is an in-page element to indicate that something is either loading or unavailable.
|
|
||||||
When "loading" is true it displays a spinner, otherwise it displays a static icon. The default
|
|
||||||
icon is a question mark in a circle.
|
|
||||||
|
|
||||||
It has two named slots, `body` and `primary`, to communicate further details about the current state
|
|
||||||
this element is meant to display.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```Typescript
|
|
||||||
import "@goauthentik/elements/EmptyState.js";
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the content of an alert _must_ be a valid HTML component; plain text does not work here.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<ak-empty-state icon="fa-eject"
|
|
||||||
><span slot="primary">This would display in the "primary" slot</span></ak-empty-state
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
### Default: Loading
|
|
||||||
|
|
||||||
The default state is _loading_
|
|
||||||
|
|
||||||
<Story of={EmptyStateStories.DefaultStory} />
|
|
||||||
|
|
||||||
### Done
|
|
||||||
|
|
||||||
<Story of={EmptyStateStories.DefaultAndLoadingDone} />
|
|
||||||
|
|
||||||
### Alternative "Done" Icon
|
|
||||||
|
|
||||||
This also shows the "header" attribute filled, which is rendered in a large, dark typeface.
|
|
||||||
|
|
||||||
<Story of={EmptyStateStories.DoneWithAlternativeIcon} />
|
|
||||||
|
|
||||||
### The Body Slot Filled
|
|
||||||
|
|
||||||
The body content slot is rendered in a lighter typeface at default size.
|
|
||||||
|
|
||||||
<Story of={EmptyStateStories.WithBodySlotFilled} />
|
|
||||||
|
|
||||||
### The Body and Primary Slot Filled
|
|
||||||
|
|
||||||
The primary content is rendered in the normal dark typeface at default size. It is also spaced
|
|
||||||
significantly below the spinner itself.
|
|
||||||
|
|
||||||
<Story of={EmptyStateStories.WithBodyAndPrimarySlotsFilled} />
|
|
@ -1,108 +1,254 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import { EmptyState, type IEmptyState } from "../EmptyState.js";
|
|
||||||
import "../EmptyState.js";
|
import "../EmptyState.js";
|
||||||
|
import { type EmptyState, type IEmptyState, akEmptyState } from "../EmptyState.js";
|
||||||
|
|
||||||
const metadata: Meta<EmptyState> = {
|
type StoryArgs = IEmptyState & {
|
||||||
title: "Elements/<ak-empty-state>",
|
headingText?: string | TemplateResult;
|
||||||
|
bodyText?: string | TemplateResult;
|
||||||
|
primaryButtonText?: string | TemplateResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadata: Meta<StoryArgs> = {
|
||||||
|
title: "Elements / <ak-empty-state>",
|
||||||
component: "ak-empty-state",
|
component: "ak-empty-state",
|
||||||
|
tags: ["autodocs"],
|
||||||
parameters: {
|
parameters: {
|
||||||
docs: {
|
docs: {
|
||||||
description: "Our empty state spinner",
|
description: {
|
||||||
|
component: `
|
||||||
|
# Empty State Component
|
||||||
|
|
||||||
|
The EmptyState is an in-page element to indicate that something is either loading or unavailable.
|
||||||
|
When "loading" is true it displays a spinner, otherwise it displays a static icon. The default
|
||||||
|
icon is a question mark in a circle.
|
||||||
|
|
||||||
|
It has three named slots:
|
||||||
|
|
||||||
|
- The default slot: The heading (renders larger and more bold)
|
||||||
|
- **body**: Any text to describe the state
|
||||||
|
- **primary**: Action buttons or other interactive elements
|
||||||
|
|
||||||
|
For the loading attributes:
|
||||||
|
|
||||||
|
- The attribute \`loading\` will show the spinner
|
||||||
|
- The attribute \`default\` will show the spinner and the default header of "Loading"
|
||||||
|
|
||||||
|
If either of these attributes is active and the element contains content not assigned to one of the
|
||||||
|
named slots, it will be shown in the header. This overrides the default text of \`default\`. You
|
||||||
|
do not need both attributes for \`default\` to work; it assumes loading.
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
layout: "padded",
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
icon: { control: "text" },
|
icon: {
|
||||||
loading: { control: "boolean" },
|
control: "text",
|
||||||
fullHeight: { control: "boolean" },
|
description: "Font Awesome icon class (without 'fa-' prefix)",
|
||||||
header: { control: "text" },
|
},
|
||||||
|
loading: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Show loading spinner instead of icon",
|
||||||
|
},
|
||||||
|
defaultLabel: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Show loading spinner instead of icon",
|
||||||
|
},
|
||||||
|
fullHeight: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Fill the full height of container",
|
||||||
|
},
|
||||||
|
headingText: {
|
||||||
|
control: "text",
|
||||||
|
description: "Text for heading slot (for demo purposes)",
|
||||||
|
},
|
||||||
|
bodyText: {
|
||||||
|
control: "text",
|
||||||
|
description: "Text for body slot (for demo purposes)",
|
||||||
|
},
|
||||||
|
primaryButtonText: {
|
||||||
|
control: "text",
|
||||||
|
description: "Text for primary button (for demo purposes)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default metadata;
|
export default metadata;
|
||||||
|
|
||||||
const container = (content: TemplateResult) =>
|
type Story = StoryObj<StoryArgs>;
|
||||||
html` <div style="background-color: #f0f0f0; padding: 1rem;">
|
|
||||||
<style>
|
|
||||||
ak-divider {
|
|
||||||
display: inline-block;
|
|
||||||
width: 32rem;
|
|
||||||
max-width: 32rem;
|
|
||||||
}</style
|
|
||||||
>${content}
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
export const DefaultStory: StoryObj = {
|
const Template: Story = {
|
||||||
args: {
|
args: {
|
||||||
icon: undefined,
|
icon: "fa-circle-radiation",
|
||||||
loading: true,
|
loading: false,
|
||||||
|
defaultLabel: false,
|
||||||
fullHeight: false,
|
fullHeight: false,
|
||||||
header: undefined,
|
|
||||||
},
|
},
|
||||||
|
render: (args) => html`
|
||||||
|
<ak-empty-state
|
||||||
|
icon=${ifDefined(args.icon)}
|
||||||
|
?loading=${args.loading}
|
||||||
|
?default=${args.defaultLabel}
|
||||||
|
?full-height=${args.fullHeight}
|
||||||
|
>
|
||||||
|
${args.headingText ? html`<span>${args.headingText}</span>` : nothing}
|
||||||
|
${args.bodyText ? html`<span slot="body">${args.bodyText}</span>` : nothing}
|
||||||
|
${args.primaryButtonText
|
||||||
|
? html`
|
||||||
|
<button slot="primary" class="pf-c-button pf-m-primary">
|
||||||
|
${args.primaryButtonText}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
</ak-empty-state>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
render: ({ icon, loading, fullHeight, header }: IEmptyState) =>
|
export const Basic: Story = {
|
||||||
container(
|
...Template,
|
||||||
html` <ak-empty-state
|
args: {
|
||||||
?loading=${loading}
|
icon: "fa-folder-open",
|
||||||
?fullHeight=${fullHeight}
|
headingText: "No files found",
|
||||||
icon=${ifDefined(icon)}
|
bodyText: "This folder is empty. Upload some files to get started.",
|
||||||
header=${ifDefined(header)}
|
},
|
||||||
>
|
};
|
||||||
</ak-empty-state>`,
|
|
||||||
|
export const Empty: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
icon: "",
|
||||||
|
},
|
||||||
|
render: () =>
|
||||||
|
html`<p>Note that a completely empty <ak-empty-state> is just that: empty.</p>
|
||||||
|
<ak-empty-state></ak-empty-state>`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithAction: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
icon: "fa-users",
|
||||||
|
headingText: "No users yet",
|
||||||
|
bodyText: "Get started by creating your first user account.",
|
||||||
|
primaryButtonText: html`<button>Create User</button>`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadingWithCustomMessage: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
loading: true,
|
||||||
|
headingText: html`<span>I <em>know</em> it's here, somewhere...</span>`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadingWithDefaultMessage: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
defaultLabel: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadingDefaultWithOverride: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
defaultLabel: true,
|
||||||
|
headingText: html`<span>Have they got a chance? Eh. It would take a miracle.</span>`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoadingDefaultWithButton: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
defaultLabel: true,
|
||||||
|
primaryButtonText: html`<button>Cancel</button>`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FullHeight: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
icon: "fa-search",
|
||||||
|
headingText: "No search results",
|
||||||
|
bodyText: "Try adjusting your search criteria or browse our categories.",
|
||||||
|
fullHeight: true,
|
||||||
|
primaryButtonText: html`<button>Go back</button>`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProgrammaticUsage: Story = {
|
||||||
|
...Template,
|
||||||
|
args: {
|
||||||
|
icon: "fa-beer",
|
||||||
|
headingText: "Hold My Beer",
|
||||||
|
bodyText: "I saw this in a cartoon once. I'm sure I can pull it off.",
|
||||||
|
primaryButtonText: html`<button>Leave The Scene Immediately</button>`,
|
||||||
|
},
|
||||||
|
render: (args) =>
|
||||||
|
akEmptyState(
|
||||||
|
{
|
||||||
|
icon: args.icon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: args.headingText,
|
||||||
|
body: args.bodyText,
|
||||||
|
primary: args.primaryButtonText
|
||||||
|
? html`
|
||||||
|
<button slot="primary" class="pf-c-button pf-m-primary">
|
||||||
|
${args.primaryButtonText}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DefaultAndLoadingDone = {
|
export const IconShowcase: Story = {
|
||||||
...DefaultStory,
|
args: {},
|
||||||
args: { ...DefaultStory, ...{ loading: false } },
|
render: () => html`
|
||||||
};
|
<div
|
||||||
|
style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;"
|
||||||
export const DoneWithAlternativeIcon = {
|
>
|
||||||
...DefaultStory,
|
<ak-empty-state icon="fa-users">
|
||||||
args: {
|
<span>Users</span>
|
||||||
...DefaultStory,
|
<span slot="body">No users found</span>
|
||||||
...{ loading: false, icon: "fa-space-shuttle", header: "The final frontier" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithBodySlotFilled = {
|
|
||||||
...DefaultStory,
|
|
||||||
args: {
|
|
||||||
...DefaultStory,
|
|
||||||
...{ loading: false, icon: "fa-space-shuttle", header: "The final frontier" },
|
|
||||||
},
|
|
||||||
render: ({ icon, loading, fullHeight, header }: IEmptyState) =>
|
|
||||||
container(html`
|
|
||||||
<ak-empty-state
|
|
||||||
?loading=${loading}
|
|
||||||
?fullHeight=${fullHeight}
|
|
||||||
icon=${ifDefined(icon)}
|
|
||||||
header=${ifDefined(header)}
|
|
||||||
>
|
|
||||||
<span slot="body">This is the body content</span>
|
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
`),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithBodyAndPrimarySlotsFilled = {
|
<ak-empty-state icon="fa-database">
|
||||||
...DefaultStory,
|
<span>Database</span>
|
||||||
args: {
|
<span slot="body">No records</span>
|
||||||
...DefaultStory,
|
</ak-empty-state>
|
||||||
...{ loading: false, icon: "fa-space-shuttle", header: "The final frontier" },
|
|
||||||
},
|
<ak-empty-state icon="fa-envelope">
|
||||||
render: ({ icon, loading, fullHeight, header }: IEmptyState) =>
|
<span>Messages</span>
|
||||||
container(
|
<span slot="body">No messages</span>
|
||||||
html` <ak-empty-state
|
</ak-empty-state>
|
||||||
?loading=${loading}
|
|
||||||
?fullHeight=${fullHeight}
|
<ak-empty-state icon="fa-chart-bar">
|
||||||
icon=${ifDefined(icon)}
|
<span>Analytics</span>
|
||||||
header=${ifDefined(header)}
|
<span slot="body">No data to display</span>
|
||||||
>
|
</ak-empty-state>
|
||||||
<span slot="body">This is the body content slot</span>
|
|
||||||
<span slot="primary">This is the primary content slot</span>
|
<ak-empty-state icon="fa-cog">
|
||||||
</ak-empty-state>`,
|
<span>Settings</span>
|
||||||
),
|
<span slot="body">No configuration</span>
|
||||||
|
</ak-empty-state>
|
||||||
|
|
||||||
|
<ak-empty-state icon="fa-shield-alt">
|
||||||
|
<span>Security</span>
|
||||||
|
<span slot="body">No alerts</span>
|
||||||
|
</ak-empty-state>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { Canvas, Description, Meta, Story, Title } from "@storybook/blocks";
|
|
||||||
|
|
||||||
import * as LoadingOverlayStories from "./LoadingOverlay.stories";
|
|
||||||
|
|
||||||
<Meta of={LoadingOverlayStories} />
|
|
||||||
|
|
||||||
# LoadingOverlay
|
|
||||||
|
|
||||||
The LoadingOverlay is meant to cover the container element completely, hiding the content behind a
|
|
||||||
dimming filter, while content loads.
|
|
||||||
|
|
||||||
It has a single named slot, "body" into which messages about the loading process can be included.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```Typescript
|
|
||||||
import "@goauthentik/elements/LoadingOverlay.js";
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the content of an alert _must_ be a valid HTML component; plain text does not work here.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<ak-loading-overlay topmost>
|
|
||||||
<span>This would display below the loading spinner</span>
|
|
||||||
</ak-loading-overlay>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
### Default
|
|
||||||
|
|
||||||
<Story of={LoadingOverlayStories.DefaultStory} />
|
|
||||||
|
|
||||||
### With a message
|
|
||||||
|
|
||||||
<Story of={LoadingOverlayStories.WithAMessage} />
|
|
@ -1,74 +1,154 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/web-components";
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
|
||||||
import { LitElement, TemplateResult, css, html } from "lit";
|
import { html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import { type ILoadingOverlay, LoadingOverlay } from "../LoadingOverlay.js";
|
|
||||||
import "../LoadingOverlay.js";
|
import "../LoadingOverlay.js";
|
||||||
|
import { type ILoadingOverlay, LoadingOverlay, akLoadingOverlay } from "../LoadingOverlay.js";
|
||||||
|
|
||||||
const metadata: Meta<LoadingOverlay> = {
|
type StoryArgs = ILoadingOverlay & {
|
||||||
title: "Elements/<ak-loading-overlay>",
|
headingText?: string;
|
||||||
|
bodyText?: string;
|
||||||
|
noSpinner: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const metadata: Meta<StoryArgs> = {
|
||||||
|
title: "Elements/ <ak-loading-overlay>",
|
||||||
component: "ak-loading-overlay",
|
component: "ak-loading-overlay",
|
||||||
|
tags: ["autodocs"],
|
||||||
parameters: {
|
parameters: {
|
||||||
docs: {
|
docs: {
|
||||||
description: "Our empty state spinner",
|
description: {
|
||||||
|
component: `
|
||||||
|
# Loading Overlay Component
|
||||||
|
|
||||||
|
A full-screen overlay component that displays a loading state with optional heading and body content.
|
||||||
|
|
||||||
|
A variant of the EmptyState component that includes a protective background for load or import
|
||||||
|
operations during which the user should be prevented from interacting with the page.
|
||||||
|
|
||||||
|
It has two named slots, both optional:
|
||||||
|
|
||||||
|
- **heading**: Main title (renders in an \`<h1>\`)
|
||||||
|
- **body**: Any text to describe the state
|
||||||
|
`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
topmost: { control: "boolean" },
|
topmost: {
|
||||||
// @ts-ignore
|
control: "boolean",
|
||||||
message: { control: "text" },
|
description:
|
||||||
|
"Whether this overlay should appear above all other overlays (z-index: 999)",
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
noSpinner: {
|
||||||
|
control: "boolean",
|
||||||
|
description: "Disable the loading spinner animation",
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
control: "text",
|
||||||
|
description: "Icon name to display instead of the default loading spinner",
|
||||||
|
},
|
||||||
|
headingText: {
|
||||||
|
control: "text",
|
||||||
|
description: "Heading text displayed above the loading indicator",
|
||||||
|
},
|
||||||
|
bodyText: {
|
||||||
|
control: "text",
|
||||||
|
description: "Body text displayed below the loading indicator",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
decorators: [
|
||||||
|
(story) => html`
|
||||||
|
<div
|
||||||
|
style="position: relative; height: 400px; width: 100%; border: 1px solid #ccc; background: #f5f5f5;"
|
||||||
|
>
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h3>Content Behind Overlay</h3>
|
||||||
|
<p>authentik is awesome (or will be if something were actually loading)</p>
|
||||||
|
<button>Sample Button</button>
|
||||||
|
</div>
|
||||||
|
${story()}
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default metadata;
|
export default metadata;
|
||||||
|
|
||||||
@customElement("ak-storybook-demo-container")
|
type Story = StoryObj<StoryArgs>;
|
||||||
export class Container extends LitElement {
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
height: 25vh;
|
|
||||||
width: 75vw;
|
|
||||||
}
|
|
||||||
#main-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property({ type: Object, attribute: false })
|
export const Default: Story = {
|
||||||
content!: TemplateResult;
|
render: () => html`<ak-loading-overlay></ak-loading-overlay>`,
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
export const WithHeading: Story = {
|
||||||
return html` <div id="main-container">${this.content}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DefaultStory: StoryObj = {
|
|
||||||
args: {
|
args: {
|
||||||
topmost: undefined,
|
headingText: "Loading Data",
|
||||||
// @ts-ignore
|
|
||||||
message: undefined,
|
|
||||||
},
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
render: ({ topmost, message }: ILoadingOverlay) => {
|
|
||||||
message = typeof message === "string" ? html`<span>${message}</span>` : message;
|
|
||||||
const content = html` <ak-loading-overlay ?topmost=${topmost}
|
|
||||||
>${message ?? ""}
|
|
||||||
</ak-loading-overlay>`;
|
|
||||||
return html`<ak-storybook-demo-container
|
|
||||||
.content=${content}
|
|
||||||
></ak-storybook-demo-container>`;
|
|
||||||
},
|
},
|
||||||
|
render: (args) =>
|
||||||
|
html`<ak-loading-overlay>
|
||||||
|
<span>${args.headingText}</span>
|
||||||
|
</ak-loading-overlay>`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithAMessage: StoryObj = {
|
export const WithHeadingAndBody: Story = {
|
||||||
...DefaultStory,
|
args: {
|
||||||
args: { ...DefaultStory.args, message: html`<p>Overlay with a message</p>` },
|
headingText: "Loading Data",
|
||||||
|
bodyText: "Please wait while we fetch your information...",
|
||||||
|
},
|
||||||
|
render: (args) =>
|
||||||
|
html`<ak-loading-overlay>
|
||||||
|
<span>${args.headingText}</span>
|
||||||
|
<span slot="body">${args.bodyText}</span>
|
||||||
|
</ak-loading-overlay>`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoSpinner: Story = {
|
||||||
|
args: {
|
||||||
|
headingText: "Static Message",
|
||||||
|
bodyText: "This overlay shows without a spinner animation.",
|
||||||
|
},
|
||||||
|
render: (args) =>
|
||||||
|
html`<ak-loading-overlay no-spinner>
|
||||||
|
<span>${args.headingText}</span>
|
||||||
|
<span slot="body">${args.bodyText}</span>
|
||||||
|
</ak-loading-overlay>`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithCustomIcon: Story = {
|
||||||
|
args: {
|
||||||
|
icon: "fa-info-circle",
|
||||||
|
headingText: "Processing",
|
||||||
|
bodyText: "Your request is being processed...",
|
||||||
|
},
|
||||||
|
render: (args) =>
|
||||||
|
html`<ak-loading-overlay no-spinner icon=${ifDefined(args.icon)}>
|
||||||
|
<span>${args.headingText}</span>
|
||||||
|
<span slot="body">${args.bodyText}</span>
|
||||||
|
</ak-loading-overlay>`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProgrammaticUsage: Story = {
|
||||||
|
args: {
|
||||||
|
topmost: false,
|
||||||
|
noSpinner: false,
|
||||||
|
icon: "",
|
||||||
|
headingText: "Programmatic Loading",
|
||||||
|
bodyText: "This overlay was created using the akLoadingOverlay function.",
|
||||||
|
},
|
||||||
|
render: (args) =>
|
||||||
|
akLoadingOverlay(
|
||||||
|
{
|
||||||
|
topmost: args.topmost,
|
||||||
|
noSpinner: args.noSpinner,
|
||||||
|
icon: args.icon || undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: args.headingText,
|
||||||
|
body: args.bodyText,
|
||||||
|
},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
@ -299,9 +299,7 @@ export abstract class Table<T> extends WithLicenseSummary(AKElement) implements
|
|||||||
return html`<tr role="row">
|
return html`<tr role="row">
|
||||||
<td role="cell" colspan="25">
|
<td role="cell" colspan="25">
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
<ak-empty-state loading
|
<ak-empty-state default-label></ak-empty-state>
|
||||||
><span slot="header">${msg("Loading")}</span></ak-empty-state
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
@ -314,7 +312,7 @@ export abstract class Table<T> extends WithLicenseSummary(AKElement) implements
|
|||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
${inner ??
|
${inner ??
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state
|
||||||
><span slot="header">${msg("No objects found.")}</span> >
|
><span>${msg("No objects found.")}</span>
|
||||||
<div slot="primary">${this.renderObjectCreate()}</div>
|
<div slot="primary">${this.renderObjectCreate()}</div>
|
||||||
</ak-empty-state>`}
|
</ak-empty-state>`}
|
||||||
</div>
|
</div>
|
||||||
@ -331,7 +329,7 @@ export abstract class Table<T> extends WithLicenseSummary(AKElement) implements
|
|||||||
if (!this.error) return nothing;
|
if (!this.error) return nothing;
|
||||||
|
|
||||||
return html`<ak-empty-state icon="fa-ban"
|
return html`<ak-empty-state icon="fa-ban"
|
||||||
><span slot="header">${msg("Failed to fetch objects.")}</span>
|
><span>${msg("Failed to fetch objects.")}</span>
|
||||||
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
<div slot="body">${pluckErrorDetail(this.error)}</div>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,8 @@ export abstract class TablePage<T> extends Table<T> {
|
|||||||
return super.renderEmpty(html`
|
return super.renderEmpty(html`
|
||||||
${inner
|
${inner
|
||||||
? inner
|
? inner
|
||||||
: html`<ak-empty-state icon=${this.pageIcon()} header="${msg("No objects found.")}">
|
: html`<ak-empty-state icon=${this.pageIcon()}
|
||||||
|
><span>${msg("No objects found.")}</span>
|
||||||
<div slot="body">
|
<div slot="body">
|
||||||
${this.searchEnabled() ? this.renderEmptyClearSearch() : html``}
|
${this.searchEnabled() ? this.renderEmptyClearSearch() : html``}
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,11 +19,7 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render the default loader", async () => {
|
it("should render the default loader", async () => {
|
||||||
render(
|
render(html`<ak-empty-state default-label></ak-empty-state>`);
|
||||||
html`<ak-empty-state loading
|
|
||||||
><span slot="header">${msg("Loading")}</span>
|
|
||||||
</ak-empty-state>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -33,25 +29,17 @@ describe("ak-empty-state", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should handle standard boolean", async () => {
|
it("should handle standard boolean", async () => {
|
||||||
render(
|
render(html`<ak-empty-state loading>Waiting</ak-empty-state>`);
|
||||||
html`<ak-empty-state loading
|
|
||||||
><span slot="header">${msg("Loading")}</span>
|
|
||||||
</ak-empty-state>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
|
|
||||||
const header = await $("ak-empty-state").$(">>>.pf-c-title");
|
const header = await $("ak-empty-state").$(">>>.pf-c-title");
|
||||||
await expect(header).toHaveText("Loading");
|
await expect(header).toHaveText("Waiting");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render a static empty state", async () => {
|
it("should render a static empty state", async () => {
|
||||||
render(
|
render(html`<ak-empty-state><span>${msg("No messages found")}</span> </ak-empty-state>`);
|
||||||
html`<ak-empty-state
|
|
||||||
><span slot="header">${msg("No messages found")}</span>
|
|
||||||
</ak-empty-state>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||||
await expect(empty).toExist();
|
await expect(empty).toExist();
|
||||||
@ -64,7 +52,7 @@ describe("ak-empty-state", () => {
|
|||||||
it("should render a slotted message", async () => {
|
it("should render a slotted message", async () => {
|
||||||
render(
|
render(
|
||||||
html`<ak-empty-state
|
html`<ak-empty-state
|
||||||
><span slot="header">${msg("No messages found")}</span>
|
><span>${msg("No messages found")}</span>
|
||||||
<p slot="body">Try again with a different filter</p>
|
<p slot="body">Try again with a different filter</p>
|
||||||
</ak-empty-state>`,
|
</ak-empty-state>`,
|
||||||
);
|
);
|
||||||
|
@ -115,9 +115,9 @@ export class UserSourceSettingsPage extends AKElement {
|
|||||||
${this.sourceSettings
|
${this.sourceSettings
|
||||||
? html`
|
? html`
|
||||||
${this.sourceSettings.length < 1
|
${this.sourceSettings.length < 1
|
||||||
? html`<ak-empty-state
|
? html`<ak-empty-state>
|
||||||
header=${msg("No services available.")}
|
<span>${msg("No services available.")}</span></ak-empty-state
|
||||||
></ak-empty-state>`
|
>`
|
||||||
: html`
|
: html`
|
||||||
${this.sourceSettings.map((source) => {
|
${this.sourceSettings.map((source) => {
|
||||||
return html`<li class="pf-c-data-list__item">
|
return html`<li class="pf-c-data-list__item">
|
||||||
@ -139,7 +139,7 @@ export class UserSourceSettingsPage extends AKElement {
|
|||||||
})}
|
})}
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
: html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`}
|
: html`<ak-empty-state default-label></ak-empty-state>`}
|
||||||
</ul>`;
|
</ul>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,7 @@ export class FlowExecutor
|
|||||||
|
|
||||||
async renderChallenge(): Promise<TemplateResult> {
|
async renderChallenge(): Promise<TemplateResult> {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
return html`<ak-empty-state loading default-label> </ak-empty-state>`;
|
||||||
}
|
}
|
||||||
switch (this.challenge?.component) {
|
switch (this.challenge?.component) {
|
||||||
case "ak-stage-access-denied":
|
case "ak-stage-access-denied":
|
||||||
|
@ -24,7 +24,7 @@ export class SessionEnd extends BaseStage<SessionEndChallenge, unknown> {
|
|||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html`<header class="pf-c-login__main-header">
|
return html`<header class="pf-c-login__main-header">
|
||||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||||
|
@ -17,10 +17,8 @@ export class DeviceCodeFinish extends BaseStage<
|
|||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state loading> </ak-empty-state>`;
|
return html`<ak-empty-state loading> </ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html`<ak-empty-state
|
return html`<ak-empty-state icon="fas fa-check">
|
||||||
icon="fas fa-check"
|
<span>${msg("You may close this page now.")}</span>
|
||||||
header=${msg("You may close this page now.")}
|
|
||||||
>
|
|
||||||
<span slot="body"> ${msg("You've successfully authenticated your device.")} </span>
|
<span slot="body"> ${msg("You've successfully authenticated your device.")} </span>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,8 @@ export class PlexLoginInit extends BaseStage<
|
|||||||
</header>
|
</header>
|
||||||
<div class="pf-c-login__main-body">
|
<div class="pf-c-login__main-body">
|
||||||
<form class="pf-c-form">
|
<form class="pf-c-form">
|
||||||
<ak-empty-state loading header=${msg("Waiting for authentication...")}>
|
<ak-empty-state loading
|
||||||
|
><span>${msg("Waiting for authentication...")}></span>
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
<hr class="pf-c-divider" />
|
<hr class="pf-c-divider" />
|
||||||
<p>${msg("If no Plex popup opens, click the button below.")}</p>
|
<p>${msg("If no Plex popup opens, click the button below.")}</p>
|
||||||
|
@ -45,12 +45,12 @@ export class FlowErrorStage extends BaseStage<FlowErrorChallenge, FlowChallengeR
|
|||||||
</header>
|
</header>
|
||||||
<div class="pf-c-login__main-body">
|
<div class="pf-c-login__main-body">
|
||||||
<form class="pf-c-form">
|
<form class="pf-c-form">
|
||||||
<ak-empty-state
|
<ak-empty-state icon="fa-times"
|
||||||
icon="fa-times"
|
><span>
|
||||||
header=${this.challenge.error
|
${this.challenge.error
|
||||||
? this.challenge.error
|
? this.challenge.error
|
||||||
: msg("Something went wrong! Please try again later.")}
|
: msg("Something went wrong! Please try again later.")}</span
|
||||||
>
|
>
|
||||||
<div slot="body">
|
<div slot="body">
|
||||||
${this.challenge?.traceback
|
${this.challenge?.traceback
|
||||||
? html`<div class="pf-c-form__group">
|
? html`<div class="pf-c-form__group">
|
||||||
|
@ -28,10 +28,10 @@ export class FlowFrameStage extends BaseStage<FrameChallenge, FrameChallengeResp
|
|||||||
</header>
|
</header>
|
||||||
<div class="pf-c-login__main-body">
|
<div class="pf-c-login__main-body">
|
||||||
${this.challenge.loadingOverlay
|
${this.challenge.loadingOverlay
|
||||||
? html`<ak-empty-state
|
? html`<ak-empty-state loading
|
||||||
loading
|
>${this.challenge.loadingText
|
||||||
header=${this.challenge.loadingText ?? undefined}
|
? html`<span>${this.challenge.loadingText}}</span>`
|
||||||
>
|
: nothing}
|
||||||
</ak-empty-state>`
|
</ak-empty-state>`
|
||||||
: nothing}
|
: nothing}
|
||||||
<iframe
|
<iframe
|
||||||
|
@ -69,13 +69,11 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
|
|||||||
// As this wouldn't really be a redirect, show a message that the page can be closed
|
// As this wouldn't really be a redirect, show a message that the page can be closed
|
||||||
// and try to close it ourselves
|
// and try to close it ourselves
|
||||||
if (!url.protocol.startsWith("http")) {
|
if (!url.protocol.startsWith("http")) {
|
||||||
return html`<ak-empty-state
|
return html`<ak-empty-state icon="fas fa-check"
|
||||||
icon="fas fa-check"
|
><span>${msg("You may close this page now.")}</span>
|
||||||
header=${msg("You may close this page now.")}
|
|
||||||
>
|
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
|
@ -44,7 +44,8 @@ export class AccessDeniedStage extends BaseStage<
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<ak-empty-state icon="fa-times" header=${msg("Request has been denied.")}>
|
<ak-empty-state icon="fa-times"
|
||||||
|
><span>${msg("Request has been denied.")}</span>
|
||||||
${this.challenge.errorMessage
|
${this.challenge.errorMessage
|
||||||
? html`
|
? html`
|
||||||
<div slot="body">
|
<div slot="body">
|
||||||
|
@ -59,13 +59,12 @@ export class AuthenticatorValidateStageWebDuo extends BaseDeviceStage<
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${this.renderUserInfo()}
|
${this.renderUserInfo()}
|
||||||
<ak-empty-state
|
<ak-empty-state ?loading="${this.authenticating}" icon="fas fa-times"
|
||||||
?loading="${this.authenticating}"
|
><span
|
||||||
header=${this.authenticating
|
>${this.authenticating
|
||||||
? msg("Sending Duo push notification...")
|
? msg("Sending Duo push notification...")
|
||||||
: errorMessage.join(", ") || msg("Failed to authenticate")}
|
: errorMessage.join(", ") || msg("Failed to authenticate")}</span
|
||||||
icon="fas fa-times"
|
>
|
||||||
>
|
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
<div class="pf-c-form__group pf-m-action">${this.renderReturnToDevicePicker()}</div>
|
<div class="pf-c-form__group pf-m-action">${this.renderReturnToDevicePicker()}</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -106,13 +106,12 @@ export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
|
|||||||
return html`<div class="pf-c-login__main-body">
|
return html`<div class="pf-c-login__main-body">
|
||||||
<form class="pf-c-form">
|
<form class="pf-c-form">
|
||||||
${this.renderUserInfo()}
|
${this.renderUserInfo()}
|
||||||
<ak-empty-state
|
<ak-empty-state ?loading="${this.authenticating}" icon="fa-times">
|
||||||
?loading="${this.authenticating}"
|
<span
|
||||||
header=${this.authenticating
|
>${this.authenticating
|
||||||
? msg("Authenticating...")
|
? msg("Authenticating...")
|
||||||
: this.errorMessage || msg("Loading")}
|
: this.errorMessage || msg("Loading")}</span
|
||||||
icon="fa-times"
|
>
|
||||||
>
|
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
<div class="pf-c-form__group pf-m-action">
|
<div class="pf-c-form__group pf-m-action">
|
||||||
${!this.authenticating
|
${!this.authenticating
|
||||||
|
@ -145,13 +145,12 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<ak-empty-state
|
<ak-empty-state ?loading="${this.registerRunning}" icon="fa-times">
|
||||||
?loading="${this.registerRunning}"
|
<span
|
||||||
header=${this.registerRunning
|
>${this.registerRunning
|
||||||
? msg("Registering...")
|
? msg("Registering...")
|
||||||
: this.registerMessage || msg("Failed to register")}
|
: this.registerMessage || msg("Failed to register")}
|
||||||
icon="fa-times"
|
</span>
|
||||||
>
|
|
||||||
</ak-empty-state>
|
</ak-empty-state>
|
||||||
${this.challenge?.responseErrors
|
${this.challenge?.responseErrors
|
||||||
? html`<p class="pf-m-block">
|
? html`<p class="pf-m-block">
|
||||||
|
@ -327,9 +327,9 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
|||||||
// [hasError, isInteractive]
|
// [hasError, isInteractive]
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
return match([Boolean(this.error), Boolean(this.challenge?.interactive)])
|
return match([Boolean(this.error), Boolean(this.challenge?.interactive)])
|
||||||
.with([true, P.any], () => akEmptyState({ icon: "fa-times", header: this.error }))
|
.with([true, P.any], () => akEmptyState({ icon: "fa-times" }, { heading: this.error }))
|
||||||
.with([false, true], () => html`${this.captchaFrame}`)
|
.with([false, true], () => html`${this.captchaFrame}`)
|
||||||
.with([false, false], () => akEmptyState({ loading: true, header: msg("Verifying...") }))
|
.with([false, false], () => akEmptyState({ loading: true }, { heading: msg("Verifying...") }))
|
||||||
.exhaustive();
|
.exhaustive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,8 +356,8 @@ export class RacInterface extends WithBrandConfig(Interface) {
|
|||||||
GuacClientState.WAITING,
|
GuacClientState.WAITING,
|
||||||
].includes(this.clientState);
|
].includes(this.clientState);
|
||||||
return html`
|
return html`
|
||||||
<ak-loading-overlay ?loading=${isLoading} icon="fa fa-times">
|
<ak-loading-overlay ?no-spinner=${!isLoading} icon="fa fa-times">
|
||||||
<span> ${message} </span>
|
<span>${message}</span>
|
||||||
</ak-loading-overlay>
|
</ak-loading-overlay>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,12 @@ export class Loading extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
|
super.connectedCallback();
|
||||||
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
this.dataset.akInterfaceRoot = this.tagName.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html` <section
|
return html`<section
|
||||||
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
||||||
>
|
>
|
||||||
<div class="pf-c-empty-state" style="height: 100vh;">
|
<div class="pf-c-empty-state" style="height: 100vh;">
|
||||||
|
@ -105,7 +105,7 @@ export class LibraryPage extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loading() {
|
loading() {
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
running() {
|
running() {
|
||||||
|
@ -172,7 +172,7 @@ export class UserSettingsFlowExecutor
|
|||||||
level: MessageLevel.success,
|
level: MessageLevel.success,
|
||||||
message: msg("Successfully updated details"),
|
message: msg("Successfully updated details"),
|
||||||
});
|
});
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
default:
|
default:
|
||||||
console.debug(
|
console.debug(
|
||||||
`authentik/user/flows: unsupported stage type ${this.challenge.component}`,
|
`authentik/user/flows: unsupported stage type ${this.challenge.component}`,
|
||||||
@ -193,7 +193,7 @@ export class UserSettingsFlowExecutor
|
|||||||
return html`<p>${msg("No settings flow configured.")}</p> `;
|
return html`<p>${msg("No settings flow configured.")}</p> `;
|
||||||
}
|
}
|
||||||
if (!this.challenge || this.loading) {
|
if (!this.challenge || this.loading) {
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html` ${this.renderChallenge()} `;
|
return html` ${this.renderChallenge()} `;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ export class UserSettingsPromptStage extends PromptStage {
|
|||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>`;
|
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||||
}
|
}
|
||||||
return html`<div class="pf-c-login__main-body">
|
return html`<div class="pf-c-login__main-body">
|
||||||
<form
|
<form
|
||||||
|
@ -13,7 +13,7 @@ import "@goauthentik/user/user-settings/mfa/MFADeviceForm";
|
|||||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
@ -130,8 +130,14 @@ export class MFADevicesPage extends Table<Device> {
|
|||||||
row(item: Device): TemplateResult[] {
|
row(item: Device): TemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`${item.name}`,
|
html`${item.name}`,
|
||||||
html`${deviceTypeName(item)}
|
html`<div>${deviceTypeName(item)}</div>
|
||||||
${item.extraDescription ? ` - ${item.extraDescription}` : ""}`,
|
${item.extraDescription
|
||||||
|
? html`
|
||||||
|
<pf-tooltip position="top" content=${item.externalId || ""}>
|
||||||
|
<small>${item.extraDescription}</small>
|
||||||
|
</pf-tooltip>
|
||||||
|
`
|
||||||
|
: nothing} `,
|
||||||
html`${item.created.getTime() > 0
|
html`${item.created.getTime() > 0
|
||||||
? html`<div>${formatElapsedTime(item.created)}</div>
|
? html`<div>${formatElapsedTime(item.created)}</div>
|
||||||
<small>${item.created.toLocaleString()}</small>`
|
<small>${item.created.toLocaleString()}</small>`
|
||||||
|
@ -8751,9 +8751,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9256,6 +9253,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -7263,9 +7263,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -7766,6 +7763,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -8814,9 +8814,6 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9317,6 +9314,9 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9231,10 +9231,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
|||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
<target>Aucun droit applicatif créé.</target>
|
<target>Aucun droit applicatif créé.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
<target>Cette application n'a actuellement pas de droit applicatif défini.</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
<target>Créer un droit</target>
|
<target>Créer un droit</target>
|
||||||
@ -9885,6 +9881,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -8966,6 +8966,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s66f572bec2bde9c4">
|
<trans-unit id="s66f572bec2bde9c4">
|
||||||
<source>External applications that use <x id="0" equiv-text="${this.brandingTitle}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
<source>External applications that use <x id="0" equiv-text="${this.brandingTitle}"/> as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.</source>
|
||||||
|
<target>Applicazioni esterne che utilizzano <x id="0" equiv-text="${this.brandingTitle}"/> come fornitore di identità tramite protocolli come OAuth2 e SAML. Qui sono mostrate tutte le applicazioni, anche quelle a cui non è possibile accedere..</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s58bec0ecd4f3ccd4">
|
<trans-unit id="s58bec0ecd4f3ccd4">
|
||||||
<source>Strict</source>
|
<source>Strict</source>
|
||||||
@ -9231,10 +9232,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
<target>Non sono stati creati entitlements per l'app.</target>
|
<target>Non sono stati creati entitlements per l'app.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
<target>Al momento, per questa applicazione non è definito alcun entitlement applicativo.</target>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
<target>Crea Privilegio</target>
|
<target>Crea Privilegio</target>
|
||||||
@ -9763,111 +9760,150 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa30ba280de276758">
|
<trans-unit id="sa30ba280de276758">
|
||||||
<source>When enabled, the SAML response will be signed.</source>
|
<source>When enabled, the SAML response will be signed.</source>
|
||||||
|
<target>Se abilitata, la risposta SAML verrà firmata.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s2c42bb3c6f5420d2">
|
<trans-unit id="s2c42bb3c6f5420d2">
|
||||||
<source>Client Certificates</source>
|
<source>Client Certificates</source>
|
||||||
|
<target>Certificati client</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s95901716c0629274">
|
<trans-unit id="s95901716c0629274">
|
||||||
<source>Available Certificates</source>
|
<source>Available Certificates</source>
|
||||||
|
<target>Certificati disponibili</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdbf6b484fa1436b0">
|
<trans-unit id="sdbf6b484fa1436b0">
|
||||||
<source>Selected Certificates</source>
|
<source>Selected Certificates</source>
|
||||||
|
<target>Certificati selezionati</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s9300147e327fd8f6">
|
<trans-unit id="s9300147e327fd8f6">
|
||||||
<source>Client-certificate/mTLS authentication/enrollment.</source>
|
<source>Client-certificate/mTLS authentication/enrollment.</source>
|
||||||
|
<target>Autenticazione/registrazione tramite certificato client/mTLS.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbf990ec848470f5c">
|
<trans-unit id="sbf990ec848470f5c">
|
||||||
<source>Certificate optional</source>
|
<source>Certificate optional</source>
|
||||||
|
<target>Certificato facoltativo</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s72e2876e9409b9eb">
|
<trans-unit id="s72e2876e9409b9eb">
|
||||||
<source>If no certificate was provided, this stage will succeed and continue to the next stage.</source>
|
<source>If no certificate was provided, this stage will succeed and continue to the next stage.</source>
|
||||||
|
<target>Se non è stato fornito alcun certificato, questa fase avrà esito positivo e si passerà alla fase successiva.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se07d973901a6e6b9">
|
<trans-unit id="se07d973901a6e6b9">
|
||||||
<source>Certificate required</source>
|
<source>Certificate required</source>
|
||||||
|
<target>Certificato richiesto</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s664c47df020ee414">
|
<trans-unit id="s664c47df020ee414">
|
||||||
<source>If no certificate was provided, this stage will stop flow execution.</source>
|
<source>If no certificate was provided, this stage will stop flow execution.</source>
|
||||||
|
<target>Se non è stato fornito alcun certificato, questa fase interromperà l'esecuzione del flusso.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbe9da6dad795aa39">
|
<trans-unit id="sbe9da6dad795aa39">
|
||||||
<source>Certificate authorities</source>
|
<source>Certificate authorities</source>
|
||||||
|
<target>Autorità di certificazione</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se3a6aa4f0ab7568e">
|
<trans-unit id="se3a6aa4f0ab7568e">
|
||||||
<source>Configure the certificate authority client certificates are validated against. The certificate authority can also be configured on a brand, which allows for different certificate authorities for different domains.</source>
|
<source>Configure the certificate authority client certificates are validated against. The certificate authority can also be configured on a brand, which allows for different certificate authorities for different domains.</source>
|
||||||
|
<target>Configurare l'autorità di certificazione con cui vengono convalidati i certificati client. L'autorità di certificazione può anche essere configurata per un marchio, consentendo di utilizzare autorità di certificazione diverse per domini diversi.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb8fdf5a09a969870">
|
<trans-unit id="sb8fdf5a09a969870">
|
||||||
<source>Certificate attribute</source>
|
<source>Certificate attribute</source>
|
||||||
|
<target>Attributo del certificato</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8069037a865037c9">
|
<trans-unit id="s8069037a865037c9">
|
||||||
<source>Configure the attribute of the certificate used to look for a user.</source>
|
<source>Configure the attribute of the certificate used to look for a user.</source>
|
||||||
|
<target>Configura l'attributo del certificato utilizzato per cercare un utente.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc185a8ad96f8eaa2">
|
<trans-unit id="sc185a8ad96f8eaa2">
|
||||||
<source>User attribute</source>
|
<source>User attribute</source>
|
||||||
|
<target>Attributo utente</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s4f9860fefb8fbe01">
|
<trans-unit id="s4f9860fefb8fbe01">
|
||||||
<source>Configure the attribute of the user used to look for a user.</source>
|
<source>Configure the attribute of the user used to look for a user.</source>
|
||||||
|
<target>Configura l'attributo dell'utente utilizzato per cercare un utente.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s42b57339ad2f3ac7">
|
<trans-unit id="s42b57339ad2f3ac7">
|
||||||
<source>Delete Not Found Objects</source>
|
<source>Delete Not Found Objects</source>
|
||||||
|
<target>Elimina oggetti non trovati</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se3b26b762110bda0">
|
<trans-unit id="se3b26b762110bda0">
|
||||||
<source>Delete authentik users and groups which were previously supplied by this source, but are now missing from it.</source>
|
<source>Delete authentik users and groups which were previously supplied by this source, but are now missing from it.</source>
|
||||||
|
<target>Elimina gli utenti e i gruppi authentik precedentemente forniti da questa fonte, ma che ora mancano.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s0a2cb398b54a6207">
|
<trans-unit id="s0a2cb398b54a6207">
|
||||||
<source>Welcome.</source>
|
<source>Welcome.</source>
|
||||||
|
<target>Benvenuto.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s4e1d2cb86cf5ecd0">
|
<trans-unit id="s4e1d2cb86cf5ecd0">
|
||||||
<source>Field which contains members of a group. The value of this field is matched against User membership attribute.</source>
|
<source>Field which contains members of a group. The value of this field is matched against User membership attribute.</source>
|
||||||
|
<target>Campo che contiene i membri di un gruppo. Il valore di questo campo viene confrontato con l'attributo di appartenenza dell'utente.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s6478025f3e0174fa">
|
<trans-unit id="s6478025f3e0174fa">
|
||||||
<source>User membership attribute</source>
|
<source>User membership attribute</source>
|
||||||
|
<target>Attributo di appartenenza dell'utente</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s344be99cf5d36407">
|
<trans-unit id="s344be99cf5d36407">
|
||||||
<source>Attribute which matches the value of Group membership field.</source>
|
<source>Attribute which matches the value of Group membership field.</source>
|
||||||
|
<target>Attributo che corrisponde al valore del campo di appartenenza al gruppo.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sfa6b7b105640e457">
|
<trans-unit id="sfa6b7b105640e457">
|
||||||
<source>Additional User DN</source>
|
<source>Additional User DN</source>
|
||||||
|
<target>DN utente aggiuntivo</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s04bb32ec9f359507">
|
<trans-unit id="s04bb32ec9f359507">
|
||||||
<source>Additional Group DN</source>
|
<source>Additional Group DN</source>
|
||||||
|
<target>DN di gruppo aggiuntivo</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb7af25ce6e30d61a">
|
<trans-unit id="sb7af25ce6e30d61a">
|
||||||
<source>The currently selected policy engine mode is <x id="0" equiv-text="${policyEngineMode.label}"/>:</source>
|
<source>The currently selected policy engine mode is <x id="0" equiv-text="${policyEngineMode.label}"/>:</source>
|
||||||
|
<target>La modalità del motore di policy attualmente selezionata è <x id="0" equiv-text="${policyEngineMode.label}"/>:</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se1d2545eda4b1600">
|
<trans-unit id="se1d2545eda4b1600">
|
||||||
<source>Import Existing Certificate-Key Pair</source>
|
<source>Import Existing Certificate-Key Pair</source>
|
||||||
|
<target>Importa coppia certificato-chiave esistente</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb3d5c0a0501669df">
|
<trans-unit id="sb3d5c0a0501669df">
|
||||||
<source>Generate New Certificate-Key Pair</source>
|
<source>Generate New Certificate-Key Pair</source>
|
||||||
|
<target>Genera nuova coppia certificato-chiave</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sf9686d31d28fcf7d">
|
<trans-unit id="sf9686d31d28fcf7d">
|
||||||
<source>Show field content</source>
|
<source>Show field content</source>
|
||||||
|
<target>Mostra il contenuto del campo</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sb1b05a7573ab618c">
|
<trans-unit id="sb1b05a7573ab618c">
|
||||||
<source>Hide field content</source>
|
<source>Hide field content</source>
|
||||||
|
<target>Nascondi il contenuto del campo</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s4f820625804ed29b">
|
<trans-unit id="s4f820625804ed29b">
|
||||||
<source>Re-authenticate with Plex</source>
|
<source>Re-authenticate with Plex</source>
|
||||||
|
<target>Riautenticare con Plex</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s0433d667ea6eec1a">
|
<trans-unit id="s0433d667ea6eec1a">
|
||||||
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
<source>The name of an invitation must be a slug: only lower case letters, numbers, and the hyphen are permitted here.</source>
|
||||||
|
<target>Il nome di un invito deve essere uno slug: sono consentiti solo lettere minuscole, numeri e trattini.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s2e9d5ea88f02ae68">
|
<trans-unit id="s2e9d5ea88f02ae68">
|
||||||
<source>Select the group of users which the alerts are sent to. </source>
|
<source>Select the group of users which the alerts are sent to. </source>
|
||||||
|
<target>Selezionare il gruppo di utenti a cui inviare gli avvisi.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="se630f2ccd39bf9e6">
|
<trans-unit id="se630f2ccd39bf9e6">
|
||||||
<source>If no group is selected and 'Send notification to event user' is disabled the rule is disabled. </source>
|
<source>If no group is selected and 'Send notification to event user' is disabled the rule is disabled. </source>
|
||||||
|
<target>Se non viene selezionato alcun gruppo e l'opzione "Invia notifica all'utente dell'evento" è disabilitata, la regola è disabilitata.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s47966b2a708694e2">
|
<trans-unit id="s47966b2a708694e2">
|
||||||
<source>Send notification to event user</source>
|
<source>Send notification to event user</source>
|
||||||
|
<target>Invia notifica all'utente dell'evento</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
<target>Se abilitata, la notifica verrà inviata all'utente che ha attivato l'evento, oltre a tutti gli utenti del gruppo sopra indicato. L'utente dell'evento sarà sempre il primo a inviare una notifica solo all'utente dell'evento che ha abilitato "Invia una volta" nel trasporto delle notifiche.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sbd65aeeb8a3b9bbc">
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
<source>Maximum registration attempts</source>
|
<source>Maximum registration attempts</source>
|
||||||
|
<target>Numero massimo di tentativi di registrazione</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
<target>Numero massimo di tentativi di registrazione consentiti. Se impostato su 0 tentativi, i tentativi non sono limitati.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -8721,9 +8721,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9224,6 +9221,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -8625,9 +8625,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9128,6 +9125,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9048,9 +9048,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9551,6 +9548,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9056,9 +9056,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9560,4 +9557,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
|
</trans-unit>
|
||||||
</body></file></xliff>
|
</body></file></xliff>
|
||||||
|
@ -9113,9 +9113,6 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="s05849efeac672dc7">
|
<trans-unit id="s05849efeac672dc7">
|
||||||
<source>No app entitlements created.</source>
|
<source>No app entitlements created.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdc8a8f29af6aa411">
|
|
||||||
<source>This application does currently not have any application entitlement defined.</source>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="sf0bd204ce3fea1de">
|
<trans-unit id="sf0bd204ce3fea1de">
|
||||||
<source>Create Entitlement</source>
|
<source>Create Entitlement</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@ -9643,6 +9640,9 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s8495753cb15e8d8e">
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sab4db6a3bd6abc1e">
|
||||||
|
<source>This application does currently not have any application entitlements defined.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user