Compare commits
	
		
			10 Commits
		
	
	
		
			providers/
			...
			web/cleanu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 27e9e892e7 | |||
| 70bf745c0c | |||
| 8b4e0361c4 | |||
| 22cb5b7379 | |||
| 2d0117d096 | |||
| 035bda4eac | |||
| 50906214e5 | |||
| e505f274b6 | |||
| fe52f44dca | |||
| 3146e5a50f | 
							
								
								
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -7,6 +7,8 @@ on: | ||||
|       - main | ||||
|       - next | ||||
|       - version-* | ||||
|     paths-ignore: | ||||
|       - website/** | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - main | ||||
|  | ||||
| @ -10,7 +10,7 @@ from rest_framework.response import Response | ||||
| from rest_framework.views import APIView | ||||
|  | ||||
| from authentik import __version__, get_build_hash | ||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version | ||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
|  | ||||
|  | ||||
| @ -19,7 +19,6 @@ class VersionSerializer(PassiveSerializer): | ||||
|  | ||||
|     version_current = SerializerMethodField() | ||||
|     version_latest = SerializerMethodField() | ||||
|     version_latest_valid = SerializerMethodField() | ||||
|     build_hash = SerializerMethodField() | ||||
|     outdated = SerializerMethodField() | ||||
|  | ||||
| @ -39,10 +38,6 @@ class VersionSerializer(PassiveSerializer): | ||||
|             return __version__ | ||||
|         return version_in_cache | ||||
|  | ||||
|     def get_version_latest_valid(self, _) -> bool: | ||||
|         """Check if latest version is valid""" | ||||
|         return cache.get(VERSION_CACHE_KEY) != VERSION_NULL | ||||
|  | ||||
|     def get_outdated(self, instance) -> bool: | ||||
|         """Check if we're running the latest version""" | ||||
|         return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance)) | ||||
|  | ||||
| @ -18,7 +18,6 @@ from authentik.lib.utils.http import get_http_session | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| VERSION_NULL = "0.0.0" | ||||
| VERSION_CACHE_KEY = "authentik_latest_version" | ||||
| VERSION_CACHE_TIMEOUT = 8 * 60 * 60  # 8 hours | ||||
| # Chop of the first ^ because we want to search the entire string | ||||
| @ -56,7 +55,7 @@ def clear_update_notifications(): | ||||
| def update_latest_version(self: SystemTask): | ||||
|     """Update latest version info""" | ||||
|     if CONFIG.get_bool("disable_update_check"): | ||||
|         cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) | ||||
|         cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) | ||||
|         self.set_status(TaskStatus.WARNING, "Version check disabled.") | ||||
|         return | ||||
|     try: | ||||
| @ -83,7 +82,7 @@ def update_latest_version(self: SystemTask): | ||||
|                 event_dict["message"] = f"Changelog: {match.group()}" | ||||
|             Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save() | ||||
|     except (RequestException, IndexError) as exc: | ||||
|         cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) | ||||
|         cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) | ||||
|         self.set_error(exc) | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -19,6 +19,8 @@ from guardian.models import UserObjectPermission | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.serializers import BaseSerializer, Serializer | ||||
| from structlog.stdlib import BoundLogger, get_logger | ||||
| from structlog.testing import capture_logs | ||||
| from structlog.types import EventDict | ||||
| from yaml import load | ||||
|  | ||||
| from authentik.blueprints.v1.common import ( | ||||
| @ -40,7 +42,6 @@ from authentik.core.models import ( | ||||
| from authentik.enterprise.license import LicenseKey | ||||
| from authentik.enterprise.models import LicenseUsage | ||||
| from authentik.enterprise.providers.rac.models import ConnectionToken | ||||
| from authentik.events.logs import LogEvent, capture_logs | ||||
| from authentik.events.models import SystemTask | ||||
| from authentik.events.utils import cleanse_dict | ||||
| from authentik.flows.models import FlowToken, Stage | ||||
| @ -160,7 +161,7 @@ class Importer: | ||||
|  | ||||
|         def updater(value) -> Any: | ||||
|             if value in self.__pk_map: | ||||
|                 self.logger.debug("Updating reference in entry", value=value) | ||||
|                 self.logger.debug("updating reference in entry", value=value) | ||||
|                 return self.__pk_map[value] | ||||
|             return value | ||||
|  | ||||
| @ -249,7 +250,7 @@ class Importer: | ||||
|         model_instance = existing_models.first() | ||||
|         if not isinstance(model(), BaseMetaModel) and model_instance: | ||||
|             self.logger.debug( | ||||
|                 "Initialise serializer with instance", | ||||
|                 "initialise serializer with instance", | ||||
|                 model=model, | ||||
|                 instance=model_instance, | ||||
|                 pk=model_instance.pk, | ||||
| @ -259,14 +260,14 @@ class Importer: | ||||
|         elif model_instance and entry.state == BlueprintEntryDesiredState.MUST_CREATED: | ||||
|             raise EntryInvalidError.from_entry( | ||||
|                 ( | ||||
|                     f"State is set to {BlueprintEntryDesiredState.MUST_CREATED} " | ||||
|                     f"state is set to {BlueprintEntryDesiredState.MUST_CREATED} " | ||||
|                     "and object exists already", | ||||
|                 ), | ||||
|                 entry, | ||||
|             ) | ||||
|         else: | ||||
|             self.logger.debug( | ||||
|                 "Initialised new serializer instance", | ||||
|                 "initialised new serializer instance", | ||||
|                 model=model, | ||||
|                 **cleanse_dict(updated_identifiers), | ||||
|             ) | ||||
| @ -323,7 +324,7 @@ class Importer: | ||||
|                 model: type[SerializerModel] = registry.get_model(model_app_label, model_name) | ||||
|             except LookupError: | ||||
|                 self.logger.warning( | ||||
|                     "App or Model does not exist", app=model_app_label, model=model_name | ||||
|                     "app or model does not exist", app=model_app_label, model=model_name | ||||
|                 ) | ||||
|                 return False | ||||
|             # Validate each single entry | ||||
| @ -335,7 +336,7 @@ class Importer: | ||||
|                 if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT: | ||||
|                     serializer = exc.serializer | ||||
|                 else: | ||||
|                     self.logger.warning(f"Entry invalid: {exc}", entry=entry, error=exc) | ||||
|                     self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc) | ||||
|                     if raise_errors: | ||||
|                         raise exc | ||||
|                     return False | ||||
| @ -355,14 +356,14 @@ class Importer: | ||||
|                     and state == BlueprintEntryDesiredState.CREATED | ||||
|                 ): | ||||
|                     self.logger.debug( | ||||
|                         "Instance exists, skipping", | ||||
|                         "instance exists, skipping", | ||||
|                         model=model, | ||||
|                         instance=instance, | ||||
|                         pk=instance.pk, | ||||
|                     ) | ||||
|                 else: | ||||
|                     instance = serializer.save() | ||||
|                     self.logger.debug("Updated model", model=instance) | ||||
|                     self.logger.debug("updated model", model=instance) | ||||
|                 if "pk" in entry.identifiers: | ||||
|                     self.__pk_map[entry.identifiers["pk"]] = instance.pk | ||||
|                 entry._state = BlueprintEntryState(instance) | ||||
| @ -370,12 +371,12 @@ class Importer: | ||||
|                 instance: Model | None = serializer.instance | ||||
|                 if instance.pk: | ||||
|                     instance.delete() | ||||
|                     self.logger.debug("Deleted model", mode=instance) | ||||
|                     self.logger.debug("deleted model", mode=instance) | ||||
|                     continue | ||||
|                 self.logger.debug("Entry to delete with no instance, skipping") | ||||
|                 self.logger.debug("entry to delete with no instance, skipping") | ||||
|         return True | ||||
|  | ||||
|     def validate(self, raise_validation_errors=False) -> tuple[bool, list[LogEvent]]: | ||||
|     def validate(self, raise_validation_errors=False) -> tuple[bool, list[EventDict]]: | ||||
|         """Validate loaded blueprint export, ensure all models are allowed | ||||
|         and serializers have no errors""" | ||||
|         self.logger.debug("Starting blueprint import validation") | ||||
| @ -389,7 +390,9 @@ class Importer: | ||||
|         ): | ||||
|             successful = self._apply_models(raise_errors=raise_validation_errors) | ||||
|             if not successful: | ||||
|                 self.logger.warning("Blueprint validation failed") | ||||
|                 self.logger.debug("Blueprint validation failed") | ||||
|         for log in logs: | ||||
|             getattr(self.logger, log.get("log_level"))(**log) | ||||
|         self.logger.debug("Finished blueprint import validation") | ||||
|         self._import = orig_import | ||||
|         return successful, logs | ||||
|  | ||||
| @ -30,7 +30,6 @@ from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata, E | ||||
| from authentik.blueprints.v1.importer import Importer | ||||
| from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE | ||||
| from authentik.blueprints.v1.oci import OCI_PREFIX | ||||
| from authentik.events.logs import capture_logs | ||||
| from authentik.events.models import TaskStatus | ||||
| from authentik.events.system_tasks import SystemTask, prefill_task | ||||
| from authentik.events.utils import sanitize_dict | ||||
| @ -212,15 +211,14 @@ def apply_blueprint(self: SystemTask, instance_pk: str): | ||||
|         if not valid: | ||||
|             instance.status = BlueprintInstanceStatus.ERROR | ||||
|             instance.save() | ||||
|             self.set_status(TaskStatus.ERROR, *logs) | ||||
|             self.set_status(TaskStatus.ERROR, *[x["event"] for x in logs]) | ||||
|             return | ||||
|         applied = importer.apply() | ||||
|         if not applied: | ||||
|             instance.status = BlueprintInstanceStatus.ERROR | ||||
|             instance.save() | ||||
|             self.set_status(TaskStatus.ERROR, "Failed to apply") | ||||
|             return | ||||
|         with capture_logs() as logs: | ||||
|             applied = importer.apply() | ||||
|             if not applied: | ||||
|                 instance.status = BlueprintInstanceStatus.ERROR | ||||
|                 instance.save() | ||||
|                 self.set_status(TaskStatus.ERROR, *logs) | ||||
|                 return | ||||
|         instance.status = BlueprintInstanceStatus.SUCCESSFUL | ||||
|         instance.last_applied_hash = file_hash | ||||
|         instance.last_applied = now() | ||||
|  | ||||
| @ -20,14 +20,15 @@ from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from structlog.stdlib import get_logger | ||||
| from structlog.testing import capture_logs | ||||
|  | ||||
| from authentik.admin.api.metrics import CoordinateSerializer | ||||
| from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT | ||||
| from authentik.core.api.providers import ProviderSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.models import Application, User | ||||
| from authentik.events.logs import LogEventSerializer, capture_logs | ||||
| from authentik.events.models import EventAction | ||||
| from authentik.events.utils import sanitize_dict | ||||
| from authentik.lib.utils.file import ( | ||||
|     FilePathSerializer, | ||||
|     FileUploadSerializer, | ||||
| @ -36,7 +37,7 @@ from authentik.lib.utils.file import ( | ||||
| ) | ||||
| from authentik.policies.api.exec import PolicyTestResultSerializer | ||||
| from authentik.policies.engine import PolicyEngine | ||||
| from authentik.policies.types import CACHE_PREFIX, PolicyResult | ||||
| from authentik.policies.types import PolicyResult | ||||
| from authentik.rbac.decorators import permission_required | ||||
| from authentik.rbac.filters import ObjectFilter | ||||
|  | ||||
| @ -45,7 +46,7 @@ LOGGER = get_logger() | ||||
|  | ||||
| def user_app_cache_key(user_pk: str) -> str: | ||||
|     """Cache key where application list for user is saved""" | ||||
|     return f"{CACHE_PREFIX}/app_access/{user_pk}" | ||||
|     return f"goauthentik.io/core/app_access/{user_pk}" | ||||
|  | ||||
|  | ||||
| class ApplicationSerializer(ModelSerializer): | ||||
| @ -181,9 +182,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|         if request.user.is_superuser: | ||||
|             log_messages = [] | ||||
|             for log in logs: | ||||
|                 if log.attributes.get("process", "") == "PolicyProcess": | ||||
|                 if log.get("process", "") == "PolicyProcess": | ||||
|                     continue | ||||
|                 log_messages.append(LogEventSerializer(log).data) | ||||
|                 log_messages.append(sanitize_dict(log)) | ||||
|             result.log_messages = log_messages | ||||
|             response = PolicyTestResultSerializer(result) | ||||
|         return Response(response.data) | ||||
| @ -213,7 +214,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|             return super().list(request) | ||||
|  | ||||
|         queryset = self._filter_queryset_for_list(self.get_queryset()) | ||||
|         paginated_apps = self.paginate_queryset(queryset) | ||||
|         pagined_apps = self.paginate_queryset(queryset) | ||||
|  | ||||
|         if "for_user" in request.query_params: | ||||
|             try: | ||||
| @ -227,18 +228,18 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|                     raise ValidationError({"for_user": "User not found"}) | ||||
|             except ValueError as exc: | ||||
|                 raise ValidationError from exc | ||||
|             allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user) | ||||
|             allowed_applications = self._get_allowed_applications(pagined_apps, user=for_user) | ||||
|             serializer = self.get_serializer(allowed_applications, many=True) | ||||
|             return self.get_paginated_response(serializer.data) | ||||
|  | ||||
|         allowed_applications = [] | ||||
|         if not should_cache: | ||||
|             allowed_applications = self._get_allowed_applications(paginated_apps) | ||||
|             allowed_applications = self._get_allowed_applications(pagined_apps) | ||||
|         if should_cache: | ||||
|             allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) | ||||
|             if not allowed_applications: | ||||
|                 LOGGER.debug("Caching allowed application list") | ||||
|                 allowed_applications = self._get_allowed_applications(paginated_apps) | ||||
|                 allowed_applications = self._get_allowed_applications(pagined_apps) | ||||
|                 cache.set( | ||||
|                     user_app_cache_key(self.request.user.pk), | ||||
|                     allowed_applications, | ||||
|  | ||||
| @ -617,9 +617,6 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel): | ||||
|         """Get serializer for this model""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         return f"User-source connection (user={self.user.username}, source={self.source.slug})" | ||||
|  | ||||
|     class Meta: | ||||
|         unique_together = (("user", "source"),) | ||||
|  | ||||
|  | ||||
| @ -16,9 +16,8 @@ from authentik.core.models import Source, SourceUserMatchingModes, User, UserSou | ||||
| from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.flows.exceptions import FlowNonApplicableException | ||||
| from authentik.flows.models import Flow, FlowToken, Stage, in_memory_stage | ||||
| from authentik.flows.models import Flow, Stage, in_memory_stage | ||||
| from authentik.flows.planner import ( | ||||
|     PLAN_CONTEXT_IS_RESTORED, | ||||
|     PLAN_CONTEXT_PENDING_USER, | ||||
|     PLAN_CONTEXT_REDIRECT, | ||||
|     PLAN_CONTEXT_SOURCE, | ||||
| @ -36,8 +35,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
| from authentik.stages.user_write.stage import PLAN_CONTEXT_USER_PATH | ||||
|  | ||||
| SESSION_KEY_OVERRIDE_FLOW_TOKEN = "authentik/flows/source_override_flow_token"  # nosec | ||||
|  | ||||
|  | ||||
| class Action(Enum): | ||||
|     """Actions that can be decided based on the request | ||||
| @ -225,43 +222,22 @@ class SourceFlowManager: | ||||
|         **kwargs, | ||||
|     ) -> HttpResponse: | ||||
|         """Prepare Authentication Plan, redirect user FlowExecutor""" | ||||
|         # Ensure redirect is carried through when user was trying to | ||||
|         # authorize application | ||||
|         final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get( | ||||
|             NEXT_ARG_NAME, "authentik_core:if-user" | ||||
|         ) | ||||
|         kwargs.update( | ||||
|             { | ||||
|                 # Since we authenticate the user by their token, they have no backend set | ||||
|                 PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT, | ||||
|                 PLAN_CONTEXT_SSO: True, | ||||
|                 PLAN_CONTEXT_SOURCE: self.source, | ||||
|                 PLAN_CONTEXT_REDIRECT: final_redirect, | ||||
|                 PLAN_CONTEXT_SOURCES_CONNECTION: connection, | ||||
|             } | ||||
|         ) | ||||
|         kwargs.update(self.policy_context) | ||||
|         if SESSION_KEY_OVERRIDE_FLOW_TOKEN in self.request.session: | ||||
|             token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN) | ||||
|             self._logger.info("Replacing source flow with overridden flow", flow=token.flow.slug) | ||||
|             plan = token.plan | ||||
|             plan.context[PLAN_CONTEXT_IS_RESTORED] = token | ||||
|             plan.context.update(kwargs) | ||||
|             for stage in self.get_stages_to_append(flow): | ||||
|                 plan.append_stage(stage) | ||||
|             if stages: | ||||
|                 for stage in stages: | ||||
|                     plan.append_stage(stage) | ||||
|             self.request.session[SESSION_KEY_PLAN] = plan | ||||
|             flow_slug = token.flow.slug | ||||
|             token.delete() | ||||
|             return redirect_with_qs( | ||||
|                 "authentik_core:if-flow", | ||||
|                 self.request.GET, | ||||
|                 flow_slug=flow_slug, | ||||
|             ) | ||||
|         # Ensure redirect is carried through when user was trying to | ||||
|         # authorize application | ||||
|         final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get( | ||||
|             NEXT_ARG_NAME, "authentik_core:if-user" | ||||
|         ) | ||||
|         if PLAN_CONTEXT_REDIRECT not in kwargs: | ||||
|             kwargs[PLAN_CONTEXT_REDIRECT] = final_redirect | ||||
|  | ||||
|         if not flow: | ||||
|             return bad_request_message( | ||||
|                 self.request, | ||||
|  | ||||
| @ -6,13 +6,13 @@ from rest_framework.filters import OrderingFilter, SearchFilter | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import GenericViewSet | ||||
|  | ||||
| from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions | ||||
| from authentik.api.authorization import OwnerFilter, OwnerPermissions | ||||
| from authentik.core.api.groups import GroupMemberSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.enterprise.api import EnterpriseRequiredMixin | ||||
| from authentik.enterprise.providers.rac.api.endpoints import EndpointSerializer | ||||
| from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer | ||||
| from authentik.enterprise.providers.rac.models import ConnectionToken | ||||
| from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint | ||||
|  | ||||
|  | ||||
| class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer): | ||||
| @ -23,7 +23,7 @@ class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer): | ||||
|     user = GroupMemberSerializer(source="session.user", read_only=True) | ||||
|  | ||||
|     class Meta: | ||||
|         model = ConnectionToken | ||||
|         model = Endpoint | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "provider", | ||||
| @ -49,5 +49,5 @@ class ConnectionTokenViewSet( | ||||
|     filterset_fields = ["endpoint", "session__user", "provider"] | ||||
|     search_fields = ["endpoint__name", "provider__name"] | ||||
|     ordering = ["endpoint__name", "provider__name"] | ||||
|     permission_classes = [OwnerSuperuserPermissions] | ||||
|     permission_classes = [OwnerPermissions] | ||||
|     filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] | ||||
|  | ||||
| @ -15,7 +15,6 @@ CELERY_BEAT_SCHEDULE = { | ||||
| TENANT_APPS = [ | ||||
|     "authentik.enterprise.audit", | ||||
|     "authentik.enterprise.providers.rac", | ||||
|     "authentik.enterprise.stages.source", | ||||
| ] | ||||
|  | ||||
| MIDDLEWARE = ["authentik.enterprise.middleware.EnterpriseMiddleware"] | ||||
|  | ||||
| @ -1,38 +0,0 @@ | ||||
| """Source Stage API Views""" | ||||
|  | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.models import Source | ||||
| from authentik.enterprise.api import EnterpriseRequiredMixin | ||||
| from authentik.enterprise.stages.source.models import SourceStage | ||||
| from authentik.flows.api.stages import StageSerializer | ||||
|  | ||||
|  | ||||
| class SourceStageSerializer(EnterpriseRequiredMixin, StageSerializer): | ||||
|     """SourceStage Serializer""" | ||||
|  | ||||
|     def validate_source(self, _source: Source) -> Source: | ||||
|         """Ensure configured source supports web-based login""" | ||||
|         source = Source.objects.filter(pk=_source.pk).select_subclasses().first() | ||||
|         if not source: | ||||
|             raise ValidationError("Invalid source") | ||||
|         login_button = source.ui_login_button(self.context["request"]) | ||||
|         if not login_button: | ||||
|             raise ValidationError("Invalid source selected, only web-based sources are supported.") | ||||
|         return source | ||||
|  | ||||
|     class Meta: | ||||
|         model = SourceStage | ||||
|         fields = StageSerializer.Meta.fields + ["source", "resume_timeout"] | ||||
|  | ||||
|  | ||||
| class SourceStageViewSet(UsedByMixin, ModelViewSet): | ||||
|     """SourceStage Viewset""" | ||||
|  | ||||
|     queryset = SourceStage.objects.all() | ||||
|     serializer_class = SourceStageSerializer | ||||
|     filterset_fields = "__all__" | ||||
|     ordering = ["name"] | ||||
|     search_fields = ["name"] | ||||
| @ -1,12 +0,0 @@ | ||||
| """authentik stage app config""" | ||||
|  | ||||
| from authentik.enterprise.apps import EnterpriseConfig | ||||
|  | ||||
|  | ||||
| class AuthentikEnterpriseStageSourceConfig(EnterpriseConfig): | ||||
|     """authentik source stage config""" | ||||
|  | ||||
|     name = "authentik.enterprise.stages.source" | ||||
|     label = "authentik_stages_source" | ||||
|     verbose_name = "authentik Enterprise.Stages.Source" | ||||
|     default = True | ||||
| @ -1,53 +0,0 @@ | ||||
| # Generated by Django 5.0.2 on 2024-02-25 20:44 | ||||
|  | ||||
| import authentik.lib.utils.time | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_core", "0033_alter_user_options"), | ||||
|         ("authentik_flows", "0027_auto_20231028_1424"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name="SourceStage", | ||||
|             fields=[ | ||||
|                 ( | ||||
|                     "stage_ptr", | ||||
|                     models.OneToOneField( | ||||
|                         auto_created=True, | ||||
|                         on_delete=django.db.models.deletion.CASCADE, | ||||
|                         parent_link=True, | ||||
|                         primary_key=True, | ||||
|                         serialize=False, | ||||
|                         to="authentik_flows.stage", | ||||
|                     ), | ||||
|                 ), | ||||
|                 ( | ||||
|                     "resume_timeout", | ||||
|                     models.TextField( | ||||
|                         default="minutes=10", | ||||
|                         help_text="Amount of time a user can take to return from the source to continue the flow (Format: hours=-1;minutes=-2;seconds=-3)", | ||||
|                         validators=[authentik.lib.utils.time.timedelta_string_validator], | ||||
|                     ), | ||||
|                 ), | ||||
|                 ( | ||||
|                     "source", | ||||
|                     models.ForeignKey( | ||||
|                         on_delete=django.db.models.deletion.CASCADE, to="authentik_core.source" | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|             options={ | ||||
|                 "verbose_name": "Source Stage", | ||||
|                 "verbose_name_plural": "Source Stages", | ||||
|             }, | ||||
|             bases=("authentik_flows.stage",), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,45 +0,0 @@ | ||||
| """Source stage models""" | ||||
|  | ||||
| from django.db import models | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views import View | ||||
| from rest_framework.serializers import BaseSerializer | ||||
|  | ||||
| from authentik.flows.models import Stage | ||||
| from authentik.lib.utils.time import timedelta_string_validator | ||||
|  | ||||
|  | ||||
| class SourceStage(Stage): | ||||
|     """Suspend the current flow execution and send the user to a source, | ||||
|     after which this flow execution is resumed.""" | ||||
|  | ||||
|     source = models.ForeignKey("authentik_core.Source", on_delete=models.CASCADE) | ||||
|  | ||||
|     resume_timeout = models.TextField( | ||||
|         default="minutes=10", | ||||
|         validators=[timedelta_string_validator], | ||||
|         help_text=_( | ||||
|             "Amount of time a user can take to return from the source to continue the flow " | ||||
|             "(Format: hours=-1;minutes=-2;seconds=-3)" | ||||
|         ), | ||||
|     ) | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[BaseSerializer]: | ||||
|         from authentik.enterprise.stages.source.api import SourceStageSerializer | ||||
|  | ||||
|         return SourceStageSerializer | ||||
|  | ||||
|     @property | ||||
|     def view(self) -> type[View]: | ||||
|         from authentik.enterprise.stages.source.stage import SourceStageView | ||||
|  | ||||
|         return SourceStageView | ||||
|  | ||||
|     @property | ||||
|     def component(self) -> str: | ||||
|         return "ak-stage-source-form" | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("Source Stage") | ||||
|         verbose_name_plural = _("Source Stages") | ||||
| @ -1,79 +0,0 @@ | ||||
| """Source stage logic""" | ||||
|  | ||||
| from typing import Any | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| from django.utils.text import slugify | ||||
| from django.utils.timezone import now | ||||
| from guardian.shortcuts import get_anonymous_user | ||||
|  | ||||
| from authentik.core.models import Source, User | ||||
| from authentik.core.sources.flow_manager import SESSION_KEY_OVERRIDE_FLOW_TOKEN | ||||
| from authentik.core.types import UILoginButton | ||||
| from authentik.enterprise.stages.source.models import SourceStage | ||||
| from authentik.flows.challenge import Challenge, ChallengeResponse | ||||
| from authentik.flows.models import FlowToken | ||||
| from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED | ||||
| from authentik.flows.stage import ChallengeStageView | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
|  | ||||
| PLAN_CONTEXT_RESUME_TOKEN = "resume_token"  # nosec | ||||
|  | ||||
|  | ||||
| class SourceStageView(ChallengeStageView): | ||||
|     """Suspend the current flow execution and send the user to a source, | ||||
|     after which this flow execution is resumed.""" | ||||
|  | ||||
|     login_button: UILoginButton | ||||
|  | ||||
|     def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: | ||||
|         current_stage: SourceStage = self.executor.current_stage | ||||
|         source: Source = ( | ||||
|             Source.objects.filter(pk=current_stage.source_id).select_subclasses().first() | ||||
|         ) | ||||
|         if not source: | ||||
|             self.logger.warning("Source does not exist") | ||||
|             return self.executor.stage_invalid("Source does not exist") | ||||
|         self.login_button = source.ui_login_button(self.request) | ||||
|         if not self.login_button: | ||||
|             self.logger.warning("Source does not have a UI login button") | ||||
|             return self.executor.stage_invalid("Invalid source") | ||||
|         restore_token = self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED) | ||||
|         override_token = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN) | ||||
|         if restore_token and override_token and restore_token.pk == override_token.pk: | ||||
|             del self.request.session[SESSION_KEY_OVERRIDE_FLOW_TOKEN] | ||||
|             return self.executor.stage_ok() | ||||
|         return super().dispatch(request, *args, **kwargs) | ||||
|  | ||||
|     def get_challenge(self, *args, **kwargs) -> Challenge: | ||||
|         resume_token = self.create_flow_token() | ||||
|         self.request.session[SESSION_KEY_OVERRIDE_FLOW_TOKEN] = resume_token | ||||
|         return self.login_button.challenge | ||||
|  | ||||
|     def create_flow_token(self) -> FlowToken: | ||||
|         """Save the current flow state in a token that can be used to resume this flow""" | ||||
|         pending_user: User = self.get_pending_user() | ||||
|         if pending_user.is_anonymous: | ||||
|             pending_user = get_anonymous_user() | ||||
|         current_stage: SourceStage = self.executor.current_stage | ||||
|         identifier = slugify(f"ak-source-stage-{current_stage.name}-{str(uuid4())}") | ||||
|         # Don't check for validity here, we only care if the token exists | ||||
|         tokens = FlowToken.objects.filter(identifier=identifier) | ||||
|         valid_delta = timedelta_from_string(current_stage.resume_timeout) | ||||
|         if not tokens.exists(): | ||||
|             return FlowToken.objects.create( | ||||
|                 expires=now() + valid_delta, | ||||
|                 user=pending_user, | ||||
|                 identifier=identifier, | ||||
|                 flow=self.executor.flow, | ||||
|                 _plan=FlowToken.pickle(self.executor.plan), | ||||
|             ) | ||||
|         token = tokens.first() | ||||
|         # Check if token is expired and rotate key if so | ||||
|         if token.is_expired: | ||||
|             token.expire_action() | ||||
|         return token | ||||
|  | ||||
|     def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: | ||||
|         return self.executor.stage_ok() | ||||
| @ -1,99 +0,0 @@ | ||||
| """Source stage tests""" | ||||
|  | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.tests.utils import create_test_flow, create_test_user | ||||
| from authentik.enterprise.stages.source.models import SourceStage | ||||
| from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken | ||||
| from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, FlowPlan | ||||
| from authentik.flows.tests import FlowTestCase | ||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.sources.saml.models import SAMLSource | ||||
| from authentik.stages.identification.models import IdentificationStage, UserFields | ||||
| from authentik.stages.password import BACKEND_INBUILT | ||||
| from authentik.stages.password.models import PasswordStage | ||||
| from authentik.stages.user_login.models import UserLoginStage | ||||
|  | ||||
|  | ||||
| class TestSourceStage(FlowTestCase): | ||||
|     """Source stage tests""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.source = SAMLSource.objects.create( | ||||
|             slug=generate_id(), | ||||
|             issuer="authentik", | ||||
|             allow_idp_initiated=True, | ||||
|             pre_authentication_flow=create_test_flow(), | ||||
|         ) | ||||
|  | ||||
|     def test_source_success(self): | ||||
|         """Test""" | ||||
|         user = create_test_user() | ||||
|         flow = create_test_flow(FlowDesignation.AUTHENTICATION) | ||||
|         stage = SourceStage.objects.create(name=generate_id(), source=self.source) | ||||
|         FlowStageBinding.objects.create( | ||||
|             target=flow, | ||||
|             stage=IdentificationStage.objects.create( | ||||
|                 name=generate_id(), | ||||
|                 user_fields=[UserFields.USERNAME], | ||||
|             ), | ||||
|             order=0, | ||||
|         ) | ||||
|         FlowStageBinding.objects.create( | ||||
|             target=flow, | ||||
|             stage=PasswordStage.objects.create(name=generate_id(), backends=[BACKEND_INBUILT]), | ||||
|             order=5, | ||||
|         ) | ||||
|         FlowStageBinding.objects.create(target=flow, stage=stage, order=10) | ||||
|         FlowStageBinding.objects.create( | ||||
|             target=flow, | ||||
|             stage=UserLoginStage.objects.create( | ||||
|                 name=generate_id(), | ||||
|             ), | ||||
|             order=15, | ||||
|         ) | ||||
|  | ||||
|         # Get user identification stage | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertStageResponse(response, flow, component="ak-stage-identification") | ||||
|         # Send username | ||||
|         response = self.client.post( | ||||
|             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||
|             data={"uid_field": user.username}, | ||||
|             follow=True, | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertStageResponse(response, flow, component="ak-stage-password") | ||||
|         # Send password | ||||
|         response = self.client.post( | ||||
|             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||
|             data={"password": user.username}, | ||||
|             follow=True, | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertStageRedirects( | ||||
|             response, | ||||
|             reverse("authentik_sources_saml:login", kwargs={"source_slug": self.source.slug}), | ||||
|         ) | ||||
|  | ||||
|         # Hijack flow plan so we don't have to emulate the source | ||||
|         flow_token = FlowToken.objects.filter( | ||||
|             identifier__startswith=f"ak-source-stage-{stage.name.lower()}" | ||||
|         ).first() | ||||
|         self.assertIsNotNone(flow_token) | ||||
|         session = self.client.session | ||||
|         plan: FlowPlan = session[SESSION_KEY_PLAN] | ||||
|         plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token | ||||
|         session[SESSION_KEY_PLAN] = plan | ||||
|         session.save() | ||||
|  | ||||
|         # Pretend we've just returned from the source | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) | ||||
| @ -1,5 +0,0 @@ | ||||
| """API URLs""" | ||||
|  | ||||
| from authentik.enterprise.stages.source.api import SourceStageViewSet | ||||
|  | ||||
| api_urlpatterns = [("stages/source", SourceStageViewSet)] | ||||
| @ -12,6 +12,7 @@ from rest_framework.fields import ( | ||||
|     ChoiceField, | ||||
|     DateTimeField, | ||||
|     FloatField, | ||||
|     ListField, | ||||
|     SerializerMethodField, | ||||
| ) | ||||
| from rest_framework.request import Request | ||||
| @ -20,7 +21,6 @@ from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.events.logs import LogEventSerializer | ||||
| from authentik.events.models import SystemTask, TaskStatus | ||||
| from authentik.rbac.decorators import permission_required | ||||
|  | ||||
| @ -39,7 +39,7 @@ class SystemTaskSerializer(ModelSerializer): | ||||
|     duration = FloatField(read_only=True) | ||||
|  | ||||
|     status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus]) | ||||
|     messages = LogEventSerializer(many=True) | ||||
|     messages = ListField(child=CharField()) | ||||
|  | ||||
|     def get_full_name(self, instance: SystemTask) -> str: | ||||
|         """Get full name with UID""" | ||||
|  | ||||
| @ -1,82 +0,0 @@ | ||||
| from collections.abc import Generator | ||||
| from contextlib import contextmanager | ||||
| from dataclasses import dataclass, field | ||||
| from datetime import datetime | ||||
| from typing import Any | ||||
|  | ||||
| from django.utils.timezone import now | ||||
| from rest_framework.fields import CharField, ChoiceField, DateTimeField, DictField | ||||
| from structlog import configure, get_config | ||||
| from structlog.stdlib import NAME_TO_LEVEL, ProcessorFormatter | ||||
| from structlog.testing import LogCapture | ||||
| from structlog.types import EventDict | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.events.utils import sanitize_dict | ||||
|  | ||||
|  | ||||
| @dataclass() | ||||
| class LogEvent: | ||||
|  | ||||
|     event: str | ||||
|     log_level: str | ||||
|     logger: str | ||||
|     timestamp: datetime = field(default_factory=now) | ||||
|     attributes: dict[str, Any] = field(default_factory=dict) | ||||
|  | ||||
|     @staticmethod | ||||
|     def from_event_dict(item: EventDict) -> "LogEvent": | ||||
|         event = item.pop("event") | ||||
|         log_level = item.pop("level").lower() | ||||
|         timestamp = datetime.fromisoformat(item.pop("timestamp")) | ||||
|         item.pop("pid", None) | ||||
|         # Sometimes log entries have both `level` and `log_level` set, but `level` is always set | ||||
|         item.pop("log_level", None) | ||||
|         return LogEvent( | ||||
|             event, log_level, item.pop("logger"), timestamp, attributes=sanitize_dict(item) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class LogEventSerializer(PassiveSerializer): | ||||
|     """Single log message with all context logged.""" | ||||
|  | ||||
|     timestamp = DateTimeField() | ||||
|     log_level = ChoiceField(choices=tuple((x, x) for x in NAME_TO_LEVEL.keys())) | ||||
|     logger = CharField() | ||||
|     event = CharField() | ||||
|     attributes = DictField() | ||||
|  | ||||
|     # TODO(2024.6?): This is a migration helper to return a correct API response for logs that | ||||
|     # have been saved in an older format (mostly just list[str] with just the messages) | ||||
|     def to_representation(self, instance): | ||||
|         if isinstance(instance, str): | ||||
|             instance = LogEvent(instance, "", "") | ||||
|         elif isinstance(instance, list): | ||||
|             instance = [LogEvent(x, "", "") for x in instance] | ||||
|         return super().to_representation(instance) | ||||
|  | ||||
|  | ||||
| @contextmanager | ||||
| def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]: | ||||
|     """Capture log entries created""" | ||||
|     logs = [] | ||||
|     cap = LogCapture() | ||||
|     # Modify `_Configuration.default_processors` set via `configure` but always | ||||
|     # keep the list instance intact to not break references held by bound | ||||
|     # loggers. | ||||
|     processors: list = get_config()["processors"] | ||||
|     old_processors = processors.copy() | ||||
|     try: | ||||
|         # clear processors list and use LogCapture for testing | ||||
|         if ProcessorFormatter.wrap_for_formatter in processors: | ||||
|             processors.remove(ProcessorFormatter.wrap_for_formatter) | ||||
|         processors.append(cap) | ||||
|         configure(processors=processors) | ||||
|         yield logs | ||||
|         for raw_log in cap.entries: | ||||
|             logs.append(LogEvent.from_event_dict(raw_log)) | ||||
|     finally: | ||||
|         # remove LogCapture and restore original processors | ||||
|         processors.clear() | ||||
|         processors.extend(old_processors) | ||||
|         configure(processors=processors) | ||||
| @ -452,13 +452,6 @@ class NotificationTransport(SerializerModel): | ||||
|  | ||||
|     def send_email(self, notification: "Notification") -> list[str]: | ||||
|         """Send notification via global email configuration""" | ||||
|         if notification.user.email.strip() == "": | ||||
|             LOGGER.info( | ||||
|                 "Discarding notification as user has no email address", | ||||
|                 user=notification.user, | ||||
|                 notification=notification, | ||||
|             ) | ||||
|             return None | ||||
|         subject_prefix = "authentik Notification: " | ||||
|         context = { | ||||
|             "key_value": { | ||||
|  | ||||
| @ -9,7 +9,6 @@ from django.utils.translation import gettext_lazy as _ | ||||
| from structlog.stdlib import get_logger | ||||
| from tenant_schemas_celery.task import TenantTask | ||||
|  | ||||
| from authentik.events.logs import LogEvent | ||||
| from authentik.events.models import Event, EventAction, TaskStatus | ||||
| from authentik.events.models import SystemTask as DBSystemTask | ||||
| from authentik.events.utils import sanitize_item | ||||
| @ -25,7 +24,7 @@ class SystemTask(TenantTask): | ||||
|     save_on_success: bool | ||||
|  | ||||
|     _status: TaskStatus | ||||
|     _messages: list[LogEvent] | ||||
|     _messages: list[str] | ||||
|  | ||||
|     _uid: str | None | ||||
|     # Precise start time from perf_counter | ||||
| @ -45,20 +44,15 @@ class SystemTask(TenantTask): | ||||
|         """Set UID, so in the case of an unexpected error its saved correctly""" | ||||
|         self._uid = uid | ||||
|  | ||||
|     def set_status(self, status: TaskStatus, *messages: LogEvent): | ||||
|     def set_status(self, status: TaskStatus, *messages: str): | ||||
|         """Set result for current run, will overwrite previous result.""" | ||||
|         self._status = status | ||||
|         self._messages = list(messages) | ||||
|         for idx, msg in enumerate(self._messages): | ||||
|             if not isinstance(msg, LogEvent): | ||||
|                 self._messages[idx] = LogEvent(msg, logger=self.__name__, log_level="info") | ||||
|         self._messages = messages | ||||
|  | ||||
|     def set_error(self, exception: Exception): | ||||
|         """Set result to error and save exception""" | ||||
|         self._status = TaskStatus.ERROR | ||||
|         self._messages = [ | ||||
|             LogEvent(exception_to_string(exception), logger=self.__name__, log_level="error") | ||||
|         ] | ||||
|         self._messages = [exception_to_string(exception)] | ||||
|  | ||||
|     def before_start(self, task_id, args, kwargs): | ||||
|         self._start_precise = perf_counter() | ||||
| @ -104,7 +98,8 @@ class SystemTask(TenantTask): | ||||
|     def on_failure(self, exc, task_id, args, kwargs, einfo): | ||||
|         super().on_failure(exc, task_id, args, kwargs, einfo=einfo) | ||||
|         if not self._status: | ||||
|             self.set_error(exc) | ||||
|             self._status = TaskStatus.ERROR | ||||
|             self._messages = exception_to_string(exc) | ||||
|         DBSystemTask.objects.update_or_create( | ||||
|             name=self.__name__, | ||||
|             uid=self._uid, | ||||
|  | ||||
| @ -47,4 +47,3 @@ class FlowStageBindingViewSet(UsedByMixin, ModelViewSet): | ||||
|     filterset_fields = "__all__" | ||||
|     search_fields = ["stage__name"] | ||||
|     ordering = ["order"] | ||||
|     ordering_fields = ["order", "stage__name"] | ||||
|  | ||||
| @ -7,7 +7,7 @@ from django.utils.translation import gettext as _ | ||||
| from drf_spectacular.types import OpenApiTypes | ||||
| from drf_spectacular.utils import OpenApiResponse, extend_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import BooleanField, CharField, ReadOnlyField | ||||
| from rest_framework.fields import BooleanField, CharField, DictField, ListField, ReadOnlyField | ||||
| from rest_framework.parsers import MultiPartParser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| @ -19,7 +19,7 @@ from authentik.blueprints.v1.exporter import FlowExporter | ||||
| from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, Importer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import CacheSerializer, LinkSerializer, PassiveSerializer | ||||
| from authentik.events.logs import LogEventSerializer | ||||
| from authentik.events.utils import sanitize_dict | ||||
| from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer | ||||
| from authentik.flows.exceptions import FlowNonApplicableException | ||||
| from authentik.flows.models import Flow | ||||
| @ -107,7 +107,7 @@ class FlowSetSerializer(FlowSerializer): | ||||
| class FlowImportResultSerializer(PassiveSerializer): | ||||
|     """Logs of an attempted flow import""" | ||||
|  | ||||
|     logs = LogEventSerializer(many=True, read_only=True) | ||||
|     logs = ListField(child=DictField(), read_only=True) | ||||
|     success = BooleanField(read_only=True) | ||||
|  | ||||
|  | ||||
| @ -184,7 +184,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet): | ||||
|  | ||||
|         importer = Importer.from_string(file.read().decode()) | ||||
|         valid, logs = importer.validate() | ||||
|         import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs] | ||||
|         import_response.initial_data["logs"] = [sanitize_dict(log) for log in logs] | ||||
|         import_response.initial_data["success"] = valid | ||||
|         import_response.is_valid() | ||||
|         if not valid: | ||||
|  | ||||
| @ -13,7 +13,6 @@ from structlog.stdlib import get_logger | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.core.types import UserSettingSerializer | ||||
| from authentik.enterprise.apps import EnterpriseConfig | ||||
| from authentik.flows.api.flows import FlowSetSerializer | ||||
| from authentik.flows.models import ConfigurableStage, Stage | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
| @ -76,7 +75,6 @@ class StageViewSet( | ||||
|                     "description": subclass.__doc__, | ||||
|                     "component": subclass().component, | ||||
|                     "model_name": subclass._meta.model_name, | ||||
|                     "requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig), | ||||
|                 } | ||||
|             ) | ||||
|         data = sorted(data, key=lambda x: x["name"]) | ||||
|  | ||||
| @ -59,11 +59,11 @@ class FlowPlan: | ||||
|     markers: list[StageMarker] = field(default_factory=list) | ||||
|  | ||||
|     def append_stage(self, stage: Stage, marker: StageMarker | None = None): | ||||
|         """Append `stage` to the end of the plan, optionally with stage marker""" | ||||
|         """Append `stage` to all stages, optionally with stage marker""" | ||||
|         return self.append(FlowStageBinding(stage=stage), marker) | ||||
|  | ||||
|     def append(self, binding: FlowStageBinding, marker: StageMarker | None = None): | ||||
|         """Append `stage` to the end of the plan, optionally with stage marker""" | ||||
|         """Append `stage` to all stages, optionally with stage marker""" | ||||
|         self.bindings.append(binding) | ||||
|         self.markers.append(marker or StageMarker()) | ||||
|  | ||||
|  | ||||
| @ -53,7 +53,6 @@ class TestFlowInspector(APITestCase): | ||||
|                     "title": flow.title, | ||||
|                     "layout": "stacked", | ||||
|                 }, | ||||
|                 "flow_designation": "authentication", | ||||
|                 "type": ChallengeTypes.NATIVE.value, | ||||
|                 "password_fields": False, | ||||
|                 "primary_action": "Log in", | ||||
|  | ||||
| @ -450,7 +450,7 @@ class FlowExecutorView(APIView): | ||||
|         return to_stage_response(self.request, challenge_view.get(self.request)) | ||||
|  | ||||
|     def cancel(self): | ||||
|         """Cancel current flow execution""" | ||||
|         """Cancel current execution and return a redirect""" | ||||
|         keys_to_delete = [ | ||||
|             SESSION_KEY_APPLICATION_PRE, | ||||
|             SESSION_KEY_PLAN, | ||||
| @ -469,7 +469,7 @@ class FlowExecutorView(APIView): | ||||
|  | ||||
|  | ||||
| class CancelView(View): | ||||
|     """View which cancels the currently active plan""" | ||||
|     """View which canels the currently active plan""" | ||||
|  | ||||
|     def get(self, request: HttpRequest) -> HttpResponse: | ||||
|         """View which canels the currently active plan""" | ||||
|  | ||||
| @ -3,9 +3,9 @@ | ||||
| from dataclasses import dataclass | ||||
|  | ||||
| from structlog.stdlib import get_logger | ||||
| from structlog.testing import capture_logs | ||||
|  | ||||
| from authentik import __version__, get_build_hash | ||||
| from authentik.events.logs import LogEvent, capture_logs | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.outposts.models import ( | ||||
| @ -63,21 +63,21 @@ class BaseController: | ||||
|         """Called by scheduled task to reconcile deployment/service/etc""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def up_with_logs(self) -> list[LogEvent]: | ||||
|     def up_with_logs(self) -> list[str]: | ||||
|         """Call .up() but capture all log output and return it.""" | ||||
|         with capture_logs() as logs: | ||||
|             self.up() | ||||
|         return logs | ||||
|         return [x["event"] for x in logs] | ||||
|  | ||||
|     def down(self): | ||||
|         """Handler to delete everything we've created""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def down_with_logs(self) -> list[LogEvent]: | ||||
|     def down_with_logs(self) -> list[str]: | ||||
|         """Call .down() but capture all log output and return it.""" | ||||
|         with capture_logs() as logs: | ||||
|             self.down() | ||||
|         return logs | ||||
|         return [x["event"] for x in logs] | ||||
|  | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|  | ||||
| @ -33,8 +33,6 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]): | ||||
|         # priority than being updated. | ||||
|         if current.spec.selector != reference.spec.selector: | ||||
|             raise NeedsUpdate() | ||||
|         if current.spec.type != reference.spec.type: | ||||
|             raise NeedsUpdate() | ||||
|         super().reconcile(current, reference) | ||||
|  | ||||
|     def get_reference_object(self) -> V1Service: | ||||
|  | ||||
| @ -9,10 +9,10 @@ from kubernetes.client.exceptions import OpenApiException | ||||
| from kubernetes.config.config_exception import ConfigException | ||||
| from kubernetes.config.incluster_config import load_incluster_config | ||||
| from kubernetes.config.kube_config import load_kube_config_from_dict | ||||
| from structlog.testing import capture_logs | ||||
| from urllib3.exceptions import HTTPError | ||||
| from yaml import dump_all | ||||
|  | ||||
| from authentik.events.logs import LogEvent, capture_logs | ||||
| from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException | ||||
| from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler | ||||
| from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler | ||||
| @ -91,7 +91,7 @@ class KubernetesController(BaseController): | ||||
|         except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: | ||||
|             raise ControllerException(str(exc)) from exc | ||||
|  | ||||
|     def up_with_logs(self) -> list[LogEvent]: | ||||
|     def up_with_logs(self) -> list[str]: | ||||
|         try: | ||||
|             all_logs = [] | ||||
|             for reconcile_key in self.reconcile_order: | ||||
| @ -104,9 +104,7 @@ class KubernetesController(BaseController): | ||||
|                         continue | ||||
|                     reconciler = reconciler_cls(self) | ||||
|                     reconciler.up() | ||||
|                 for log in logs: | ||||
|                     log.logger = reconcile_key.title() | ||||
|                 all_logs.extend(logs) | ||||
|                 all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs] | ||||
|             return all_logs | ||||
|         except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: | ||||
|             raise ControllerException(str(exc)) from exc | ||||
| @ -124,7 +122,7 @@ class KubernetesController(BaseController): | ||||
|         except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: | ||||
|             raise ControllerException(str(exc)) from exc | ||||
|  | ||||
|     def down_with_logs(self) -> list[LogEvent]: | ||||
|     def down_with_logs(self) -> list[str]: | ||||
|         try: | ||||
|             all_logs = [] | ||||
|             for reconcile_key in self.reconcile_order: | ||||
| @ -137,9 +135,7 @@ class KubernetesController(BaseController): | ||||
|                         continue | ||||
|                     reconciler = reconciler_cls(self) | ||||
|                     reconciler.down() | ||||
|                 for log in logs: | ||||
|                     log.logger = reconcile_key.title() | ||||
|                 all_logs.extend(logs) | ||||
|                 all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs] | ||||
|             return all_logs | ||||
|         except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: | ||||
|             raise ControllerException(str(exc)) from exc | ||||
|  | ||||
| @ -149,8 +149,10 @@ def outpost_controller( | ||||
|         if not controller_type: | ||||
|             return | ||||
|         with controller_type(outpost, outpost.service_connection) as controller: | ||||
|             LOGGER.debug("---------------Outpost Controller logs starting----------------") | ||||
|             logs = getattr(controller, f"{action}_with_logs")() | ||||
|             LOGGER.debug("---------------Outpost Controller logs starting----------------") | ||||
|             for log in logs: | ||||
|                 LOGGER.debug(log) | ||||
|             LOGGER.debug("-----------------Outpost Controller logs end-------------------") | ||||
|     except (ControllerException, ServiceConnectionInvalid) as exc: | ||||
|         self.set_error(exc) | ||||
|  | ||||
| @ -1,11 +1,10 @@ | ||||
| """Serializer for policy execution""" | ||||
|  | ||||
| from rest_framework.fields import BooleanField, CharField, ListField | ||||
| from rest_framework.fields import BooleanField, CharField, DictField, ListField | ||||
| from rest_framework.relations import PrimaryKeyRelatedField | ||||
|  | ||||
| from authentik.core.api.utils import JSONDictField, PassiveSerializer | ||||
| from authentik.core.models import User | ||||
| from authentik.events.logs import LogEventSerializer | ||||
|  | ||||
|  | ||||
| class PolicyTestSerializer(PassiveSerializer): | ||||
| @ -20,4 +19,4 @@ class PolicyTestResultSerializer(PassiveSerializer): | ||||
|  | ||||
|     passing = BooleanField() | ||||
|     messages = ListField(child=CharField(), read_only=True) | ||||
|     log_messages = LogEventSerializer(many=True, read_only=True) | ||||
|     log_messages = ListField(child=DictField(), read_only=True) | ||||
|  | ||||
| @ -11,11 +11,12 @@ from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import GenericViewSet | ||||
| from structlog.stdlib import get_logger | ||||
| from structlog.testing import capture_logs | ||||
|  | ||||
| from authentik.core.api.applications import user_app_cache_key | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import CacheSerializer, MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.events.logs import LogEventSerializer, capture_logs | ||||
| from authentik.events.utils import sanitize_dict | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
| from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer | ||||
| from authentik.policies.models import Policy, PolicyBinding | ||||
| @ -165,9 +166,9 @@ class PolicyViewSet( | ||||
|             result = proc.execute() | ||||
|         log_messages = [] | ||||
|         for log in logs: | ||||
|             if log.attributes.get("process", "") == "PolicyProcess": | ||||
|             if log.get("process", "") == "PolicyProcess": | ||||
|                 continue | ||||
|             log_messages.append(LogEventSerializer(log).data) | ||||
|             log_messages.append(sanitize_dict(log)) | ||||
|         result.log_messages = log_messages | ||||
|         response = PolicyTestResultSerializer(result) | ||||
|         return Response(response.data) | ||||
|  | ||||
| @ -13,7 +13,6 @@ from authentik.events.context_processors.base import get_context_processors | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from authentik.core.models import User | ||||
|     from authentik.events.logs import LogEvent | ||||
|     from authentik.policies.models import PolicyBinding | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| @ -75,7 +74,7 @@ class PolicyResult: | ||||
|     source_binding: PolicyBinding | None | ||||
|     source_results: list[PolicyResult] | None | ||||
|  | ||||
|     log_messages: list[LogEvent] | None | ||||
|     log_messages: list[dict] | None | ||||
|  | ||||
|     def __init__(self, passing: bool, *messages: str): | ||||
|         self.passing = passing | ||||
|  | ||||
| @ -25,7 +25,7 @@ class OAuthDeviceCodeFinishChallengeResponse(ChallengeResponse): | ||||
|  | ||||
|  | ||||
| class OAuthDeviceCodeFinishStage(ChallengeStageView): | ||||
|     """Stage to finish the OAuth device code flow""" | ||||
|     """Stage show at the end of a device flow""" | ||||
|  | ||||
|     response_class = OAuthDeviceCodeFinishChallengeResponse | ||||
|  | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views import View | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.exceptions import ErrorDetail | ||||
| from rest_framework.fields import CharField, IntegerField | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| @ -57,7 +57,6 @@ def validate_code(code: int, request: HttpRequest) -> HttpResponse | None: | ||||
|     scope_descriptions = UserInfoView().get_scope_descriptions(token.scope, token.provider) | ||||
|     planner = FlowPlanner(token.provider.authorization_flow) | ||||
|     planner.allow_empty_flows = True | ||||
|     planner.use_cache = False | ||||
|     try: | ||||
|         plan = planner.plan( | ||||
|             request, | ||||
| @ -129,13 +128,6 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse): | ||||
|     code = IntegerField() | ||||
|     component = CharField(default="ak-provider-oauth2-device-code") | ||||
|  | ||||
|     def validate_code(self, code: int) -> HttpResponse | None: | ||||
|         """Validate code and save the returned http response""" | ||||
|         response = validate_code(code, self.stage.request) | ||||
|         if not response: | ||||
|             raise ValidationError("Invalid code", "invalid") | ||||
|         return response | ||||
|  | ||||
|  | ||||
| class OAuthDeviceCodeStage(ChallengeStageView): | ||||
|     """Flow challenge for users to enter device codes""" | ||||
| @ -151,4 +143,12 @@ class OAuthDeviceCodeStage(ChallengeStageView): | ||||
|         ) | ||||
|  | ||||
|     def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: | ||||
|         return response.validated_data["code"] | ||||
|         code = response.validated_data["code"] | ||||
|         validation = validate_code(code, self.request) | ||||
|         if not validation: | ||||
|             response._errors.setdefault("code", []) | ||||
|             response._errors["code"].append(ErrorDetail(_("Invalid code"), "invalid")) | ||||
|             return self.challenge_invalid(response) | ||||
|         # Run cancel to cleanup the current flow | ||||
|         self.executor.cancel() | ||||
|         return validation | ||||
|  | ||||
| @ -56,10 +56,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]): | ||||
|             proxy_provider: ProxyProvider | ||||
|             external_host_name = urlparse(proxy_provider.external_host) | ||||
|             expected_hosts.append(external_host_name.hostname) | ||||
|             if ( | ||||
|                 external_host_name.scheme == "https" | ||||
|                 and self.controller.outpost.config.kubernetes_ingress_secret_name | ||||
|             ): | ||||
|             if external_host_name.scheme == "https": | ||||
|                 expected_hosts_tls.append(external_host_name.hostname) | ||||
|         expected_hosts.sort() | ||||
|         expected_hosts_tls.sort() | ||||
| @ -119,10 +116,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]): | ||||
|         ): | ||||
|             proxy_provider: ProxyProvider | ||||
|             external_host_name = urlparse(proxy_provider.external_host) | ||||
|             if ( | ||||
|                 external_host_name.scheme == "https" | ||||
|                 and self.controller.outpost.config.kubernetes_ingress_secret_name | ||||
|             ): | ||||
|             if external_host_name.scheme == "https": | ||||
|                 tls_hosts.append(external_host_name.hostname) | ||||
|             if proxy_provider.mode in [ | ||||
|                 ProxyMode.FORWARD_SINGLE, | ||||
| @ -166,15 +160,13 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]): | ||||
|             rules.append(rule) | ||||
|         tls_config = None | ||||
|         if tls_hosts: | ||||
|             tls_config = [ | ||||
|                 V1IngressTLS( | ||||
|                     hosts=tls_hosts, | ||||
|                     secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name, | ||||
|                 ) | ||||
|             ] | ||||
|             tls_config = V1IngressTLS( | ||||
|                 hosts=tls_hosts, | ||||
|                 secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name, | ||||
|             ) | ||||
|         spec = V1IngressSpec( | ||||
|             rules=rules, | ||||
|             tls=tls_config, | ||||
|             tls=[tls_config], | ||||
|         ) | ||||
|         if self.controller.outpost.config.kubernetes_ingress_class_name: | ||||
|             spec.ingress_class_name = self.controller.outpost.config.kubernetes_ingress_class_name | ||||
|  | ||||
| @ -196,10 +196,8 @@ if CONFIG.get_bool("redis.tls", False): | ||||
|     _redis_protocol_prefix = "rediss://" | ||||
|     _redis_celery_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}" | ||||
| _redis_url = ( | ||||
|     f"{_redis_protocol_prefix}" | ||||
|     f"{quote_plus(CONFIG.get('redis.username'))}:" | ||||
|     f"{quote_plus(CONFIG.get('redis.password'))}@" | ||||
|     f"{quote_plus(CONFIG.get('redis.host'))}:" | ||||
|     f"{_redis_protocol_prefix}:" | ||||
|     f"{quote_plus(CONFIG.get('redis.password'))}@{quote_plus(CONFIG.get('redis.host'))}:" | ||||
|     f"{CONFIG.get_int('redis.port')}" | ||||
| ) | ||||
|  | ||||
|  | ||||
| @ -77,7 +77,6 @@ class LDAPSourceSerializer(SourceSerializer): | ||||
|             "group_object_filter", | ||||
|             "group_membership_field", | ||||
|             "object_uniqueness_field", | ||||
|             "password_login_update_internal_password", | ||||
|             "sync_users", | ||||
|             "sync_users_password", | ||||
|             "sync_groups", | ||||
| @ -119,7 +118,6 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): | ||||
|         "group_object_filter", | ||||
|         "group_membership_field", | ||||
|         "object_uniqueness_field", | ||||
|         "password_login_update_internal_password", | ||||
|         "sync_users", | ||||
|         "sync_users_password", | ||||
|         "sync_groups", | ||||
|  | ||||
| @ -41,11 +41,10 @@ class LDAPBackend(InbuiltBackend): | ||||
|         # or has a password, but couldn't be authenticated by ModelBackend. | ||||
|         # This means we check with a bind to see if the LDAP password has changed | ||||
|         if self.auth_user_by_bind(source, user, password): | ||||
|             if source.password_login_update_internal_password: | ||||
|                 # Password given successfully binds to LDAP, so we save it in our Database | ||||
|                 LOGGER.debug("Updating user's password in DB", user=user) | ||||
|                 user.set_password(password, signal=False) | ||||
|                 user.save() | ||||
|             # Password given successfully binds to LDAP, so we save it in our Database | ||||
|             LOGGER.debug("Updating user's password in DB", user=user) | ||||
|             user.set_password(password, signal=False) | ||||
|             user.save() | ||||
|             return user | ||||
|         # Password doesn't match | ||||
|         LOGGER.debug("Failed to bind, password invalid") | ||||
|  | ||||
| @ -1,29 +0,0 @@ | ||||
| # Generated by Django 5.0.1 on 2024-01-31 18:41 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_sources_ldap", "0003_ldapsource_client_certificate_ldapsource_sni_and_more"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="ldapsource", | ||||
|             name="password_login_update_internal_password", | ||||
|             field=models.BooleanField( | ||||
|                 default=True, | ||||
|                 help_text="Update internal authentik password when login succeeds with LDAP", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="ldapsource", | ||||
|             name="password_login_update_internal_password", | ||||
|             field=models.BooleanField( | ||||
|                 default=False, | ||||
|                 help_text="Update internal authentik password when login succeeds with LDAP", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -98,11 +98,6 @@ class LDAPSource(Source): | ||||
|         help_text=_("Property mappings used for group creation/updating."), | ||||
|     ) | ||||
|  | ||||
|     password_login_update_internal_password = models.BooleanField( | ||||
|         default=False, | ||||
|         help_text=_("Update internal authentik password when login succeeds with LDAP"), | ||||
|     ) | ||||
|  | ||||
|     sync_users = models.BooleanField(default=True) | ||||
|     sync_users_password = models.BooleanField( | ||||
|         default=True, | ||||
|  | ||||
| @ -47,7 +47,7 @@ class SourceType: | ||||
|     def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge: | ||||
|         """Allow types to return custom challenges""" | ||||
|         return RedirectChallenge( | ||||
|             data={ | ||||
|             instance={ | ||||
|                 "type": ChallengeTypes.REDIRECT.value, | ||||
|                 "to": reverse( | ||||
|                     "authentik_sources_oauth:oauth-client-login", | ||||
|  | ||||
| @ -54,7 +54,7 @@ class OAuthCallback(OAuthClientMixin, View): | ||||
|                 raw_profile=exc.doc, | ||||
|             ).from_http(self.request) | ||||
|             return self.handle_login_failure("Could not retrieve profile.") | ||||
|         identifier = self.get_user_id(info=raw_info) | ||||
|         identifier = self.get_user_id(raw_info) | ||||
|         if identifier is None: | ||||
|             return self.handle_login_failure("Could not determine id.") | ||||
|         # Get or create access record | ||||
| @ -67,7 +67,6 @@ class OAuthCallback(OAuthClientMixin, View): | ||||
|         ) | ||||
|         sfm.policy_context = {"oauth_userinfo": raw_info} | ||||
|         return sfm.get_flow( | ||||
|             raw_info=raw_info, | ||||
|             access_token=self.token.get("access_token"), | ||||
|         ) | ||||
|  | ||||
| @ -117,7 +116,6 @@ class OAuthSourceFlowManager(SourceFlowManager): | ||||
|         self, | ||||
|         connection: UserOAuthSourceConnection, | ||||
|         access_token: str | None = None, | ||||
|         **_, | ||||
|     ) -> UserOAuthSourceConnection: | ||||
|         """Set the access_token on the connection""" | ||||
|         connection.access_token = access_token | ||||
|  | ||||
| @ -190,7 +190,7 @@ class SAMLSource(Source): | ||||
|     def ui_login_button(self, request: HttpRequest) -> UILoginButton: | ||||
|         return UILoginButton( | ||||
|             challenge=RedirectChallenge( | ||||
|                 data={ | ||||
|                 instance={ | ||||
|                     "type": ChallengeTypes.REDIRECT.value, | ||||
|                     "to": reverse( | ||||
|                         "authentik_sources_saml:login", | ||||
|  | ||||
| @ -234,14 +234,12 @@ class ResponseProcessor: | ||||
|         if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT: | ||||
|             return self._handle_name_id_transient() | ||||
|  | ||||
|         flow_manager = SAMLSourceFlowManager( | ||||
|         return SAMLSourceFlowManager( | ||||
|             self._source, | ||||
|             self._http_request, | ||||
|             name_id.text, | ||||
|             delete_none_values(self.get_attributes()), | ||||
|         ) | ||||
|         flow_manager.policy_context["saml_response"] = self._root | ||||
|         return flow_manager | ||||
|  | ||||
|  | ||||
| class SAMLSourceFlowManager(SourceFlowManager): | ||||
|  | ||||
| @ -120,9 +120,7 @@ def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Dev | ||||
|             stage=stage_view.executor.current_stage, | ||||
|             device_class=DeviceClasses.TOTP.value, | ||||
|         ) | ||||
|         raise ValidationError( | ||||
|             _("Invalid Token. Please ensure the time on your device is accurate and try again.") | ||||
|         ) | ||||
|         raise ValidationError(_("Invalid Token")) | ||||
|     return device | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -12,7 +12,6 @@ class DummyChallenge(Challenge): | ||||
|     """Dummy challenge""" | ||||
|  | ||||
|     component = CharField(default="ak-stage-dummy") | ||||
|     name = CharField() | ||||
|  | ||||
|  | ||||
| class DummyChallengeResponse(ChallengeResponse): | ||||
| @ -36,6 +35,5 @@ class DummyStageView(ChallengeStageView): | ||||
|             data={ | ||||
|                 "type": ChallengeTypes.NATIVE.value, | ||||
|                 "title": self.executor.current_stage.name, | ||||
|                 "name": self.executor.current_stage.name, | ||||
|             } | ||||
|         ) | ||||
|  | ||||
| @ -10,7 +10,7 @@ from django.db.models import Q | ||||
| from django.http import HttpResponse | ||||
| from django.utils.translation import gettext as _ | ||||
| from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field | ||||
| from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, ListField | ||||
| from rest_framework.fields import BooleanField, CharField, DictField, ListField | ||||
| from rest_framework.serializers import ValidationError | ||||
| from sentry_sdk.hub import Hub | ||||
|  | ||||
| @ -66,7 +66,6 @@ class IdentificationChallenge(Challenge): | ||||
|     user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True) | ||||
|     password_fields = BooleanField() | ||||
|     application_pre = CharField(required=False) | ||||
|     flow_designation = ChoiceField(FlowDesignation.choices) | ||||
|  | ||||
|     enroll_url = CharField(required=False) | ||||
|     recovery_url = CharField(required=False) | ||||
| @ -195,12 +194,11 @@ class IdentificationStageView(ChallengeStageView): | ||||
|         challenge = IdentificationChallenge( | ||||
|             data={ | ||||
|                 "type": ChallengeTypes.NATIVE.value, | ||||
|                 "component": "ak-stage-identification", | ||||
|                 "primary_action": self.get_primary_action(), | ||||
|                 "component": "ak-stage-identification", | ||||
|                 "user_fields": current_stage.user_fields, | ||||
|                 "password_fields": bool(current_stage.password_stage), | ||||
|                 "show_source_labels": current_stage.show_source_labels, | ||||
|                 "flow_designation": self.executor.flow.designation, | ||||
|             } | ||||
|         ) | ||||
|         # If the user has been redirected to us whilst trying to access an | ||||
| @ -239,9 +237,7 @@ class IdentificationStageView(ChallengeStageView): | ||||
|             ui_login_button = source.ui_login_button(self.request) | ||||
|             if ui_login_button: | ||||
|                 button = asdict(ui_login_button) | ||||
|                 source_challenge = ui_login_button.challenge | ||||
|                 source_challenge.is_valid() | ||||
|                 button["challenge"] = source_challenge.data | ||||
|                 button["challenge"] = ui_login_button.challenge.data | ||||
|                 ui_sources.append(button) | ||||
|         challenge.initial_data["sources"] = ui_sources | ||||
|         return challenge | ||||
|  | ||||
| @ -12,7 +12,6 @@ from rest_framework.exceptions import ValidationError | ||||
| from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER | ||||
| from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes | ||||
| from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION | ||||
| from authentik.events.utils import sanitize_item | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||
| from authentik.flows.stage import StageView | ||||
| from authentik.flows.views.executor import FlowExecutorView | ||||
| @ -48,7 +47,7 @@ class UserWriteStageView(StageView): | ||||
|         # this is just a sanity check to ensure that is removed | ||||
|         if parts[0] == "attributes": | ||||
|             parts = parts[1:] | ||||
|         set_path_in_dict(user.attributes, ".".join(parts), sanitize_item(value)) | ||||
|         set_path_in_dict(user.attributes, ".".join(parts), value) | ||||
|  | ||||
|     def ensure_user(self) -> tuple[User | None, bool]: | ||||
|         """Ensure a user exists""" | ||||
|  | ||||
| @ -2594,43 +2594,6 @@ | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "object", | ||||
|                         "required": [ | ||||
|                             "model", | ||||
|                             "identifiers" | ||||
|                         ], | ||||
|                         "properties": { | ||||
|                             "model": { | ||||
|                                 "const": "authentik_stages_source.sourcestage" | ||||
|                             }, | ||||
|                             "id": { | ||||
|                                 "type": "string" | ||||
|                             }, | ||||
|                             "state": { | ||||
|                                 "type": "string", | ||||
|                                 "enum": [ | ||||
|                                     "absent", | ||||
|                                     "present", | ||||
|                                     "created", | ||||
|                                     "must_created" | ||||
|                                 ], | ||||
|                                 "default": "present" | ||||
|                             }, | ||||
|                             "conditions": { | ||||
|                                 "type": "array", | ||||
|                                 "items": { | ||||
|                                     "type": "boolean" | ||||
|                                 } | ||||
|                             }, | ||||
|                             "attrs": { | ||||
|                                 "$ref": "#/$defs/model_authentik_stages_source.sourcestage" | ||||
|                             }, | ||||
|                             "identifiers": { | ||||
|                                 "$ref": "#/$defs/model_authentik_stages_source.sourcestage" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "object", | ||||
|                         "required": [ | ||||
| @ -3294,7 +3257,6 @@ | ||||
|                         "authentik.enterprise", | ||||
|                         "authentik.enterprise.audit", | ||||
|                         "authentik.enterprise.providers.rac", | ||||
|                         "authentik.enterprise.stages.source", | ||||
|                         "authentik.events" | ||||
|                     ], | ||||
|                     "title": "App", | ||||
| @ -3376,7 +3338,6 @@ | ||||
|                         "authentik_providers_rac.racprovider", | ||||
|                         "authentik_providers_rac.endpoint", | ||||
|                         "authentik_providers_rac.racpropertymapping", | ||||
|                         "authentik_stages_source.sourcestage", | ||||
|                         "authentik_events.event", | ||||
|                         "authentik_events.notificationtransport", | ||||
|                         "authentik_events.notification", | ||||
| @ -4347,11 +4308,6 @@ | ||||
|                     "title": "Object uniqueness field", | ||||
|                     "description": "Field which contains a unique Identifier." | ||||
|                 }, | ||||
|                 "password_login_update_internal_password": { | ||||
|                     "type": "boolean", | ||||
|                     "title": "Password login update internal password", | ||||
|                     "description": "Update internal authentik password when login succeeds with LDAP" | ||||
|                 }, | ||||
|                 "sync_users": { | ||||
|                     "type": "boolean", | ||||
|                     "title": "Sync users" | ||||
| @ -8062,109 +8018,6 @@ | ||||
|             }, | ||||
|             "required": [] | ||||
|         }, | ||||
|         "model_authentik_stages_source.sourcestage": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "name": { | ||||
|                     "type": "string", | ||||
|                     "minLength": 1, | ||||
|                     "title": "Name" | ||||
|                 }, | ||||
|                 "flow_set": { | ||||
|                     "type": "array", | ||||
|                     "items": { | ||||
|                         "type": "object", | ||||
|                         "properties": { | ||||
|                             "name": { | ||||
|                                 "type": "string", | ||||
|                                 "minLength": 1, | ||||
|                                 "title": "Name" | ||||
|                             }, | ||||
|                             "slug": { | ||||
|                                 "type": "string", | ||||
|                                 "maxLength": 50, | ||||
|                                 "minLength": 1, | ||||
|                                 "pattern": "^[-a-zA-Z0-9_]+$", | ||||
|                                 "title": "Slug", | ||||
|                                 "description": "Visible in the URL." | ||||
|                             }, | ||||
|                             "title": { | ||||
|                                 "type": "string", | ||||
|                                 "minLength": 1, | ||||
|                                 "title": "Title", | ||||
|                                 "description": "Shown as the Title in Flow pages." | ||||
|                             }, | ||||
|                             "designation": { | ||||
|                                 "type": "string", | ||||
|                                 "enum": [ | ||||
|                                     "authentication", | ||||
|                                     "authorization", | ||||
|                                     "invalidation", | ||||
|                                     "enrollment", | ||||
|                                     "unenrollment", | ||||
|                                     "recovery", | ||||
|                                     "stage_configuration" | ||||
|                                 ], | ||||
|                                 "title": "Designation", | ||||
|                                 "description": "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik." | ||||
|                             }, | ||||
|                             "policy_engine_mode": { | ||||
|                                 "type": "string", | ||||
|                                 "enum": [ | ||||
|                                     "all", | ||||
|                                     "any" | ||||
|                                 ], | ||||
|                                 "title": "Policy engine mode" | ||||
|                             }, | ||||
|                             "compatibility_mode": { | ||||
|                                 "type": "boolean", | ||||
|                                 "title": "Compatibility mode", | ||||
|                                 "description": "Enable compatibility mode, increases compatibility with password managers on mobile devices." | ||||
|                             }, | ||||
|                             "layout": { | ||||
|                                 "type": "string", | ||||
|                                 "enum": [ | ||||
|                                     "stacked", | ||||
|                                     "content_left", | ||||
|                                     "content_right", | ||||
|                                     "sidebar_left", | ||||
|                                     "sidebar_right" | ||||
|                                 ], | ||||
|                                 "title": "Layout" | ||||
|                             }, | ||||
|                             "denied_action": { | ||||
|                                 "type": "string", | ||||
|                                 "enum": [ | ||||
|                                     "message_continue", | ||||
|                                     "message", | ||||
|                                     "continue" | ||||
|                                 ], | ||||
|                                 "title": "Denied action", | ||||
|                                 "description": "Configure what should happen when a flow denies access to a user." | ||||
|                             } | ||||
|                         }, | ||||
|                         "required": [ | ||||
|                             "name", | ||||
|                             "slug", | ||||
|                             "title", | ||||
|                             "designation" | ||||
|                         ] | ||||
|                     }, | ||||
|                     "title": "Flow set" | ||||
|                 }, | ||||
|                 "source": { | ||||
|                     "type": "integer", | ||||
|                     "title": "Source" | ||||
|                 }, | ||||
|                 "resume_timeout": { | ||||
|                     "type": "string", | ||||
|                     "minLength": 1, | ||||
|                     "title": "Resume timeout", | ||||
|                     "description": "Amount of time a user can take to return from the source to continue the flow (Format: hours=-1;minutes=-2;seconds=-3)" | ||||
|                 } | ||||
|             }, | ||||
|             "required": [] | ||||
|         }, | ||||
|         "model_authentik_events.event": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|  | ||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @ -30,7 +30,7 @@ require ( | ||||
| 	github.com/spf13/cobra v1.8.0 | ||||
| 	github.com/stretchr/testify v1.9.0 | ||||
| 	github.com/wwt/guac v1.3.2 | ||||
| 	goauthentik.io/api/v3 v3.2024022.7 | ||||
| 	goauthentik.io/api/v3 v3.2024022.1 | ||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||
| 	golang.org/x/oauth2 v0.18.0 | ||||
| 	golang.org/x/sync v0.6.0 | ||||
|  | ||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @ -280,8 +280,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.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= | ||||
| go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= | ||||
| goauthentik.io/api/v3 v3.2024022.7 h1:VR9OmcZvTzPSjit2Dx2EoHrLc9v9XRyjPXNpnGISWWM= | ||||
| goauthentik.io/api/v3 v3.2024022.7/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||
| goauthentik.io/api/v3 v3.2024022.1 h1:ydYi3X/OSnu4LumUN+oCe6vvGDXil1Xn186hC9FQb4Q= | ||||
| goauthentik.io/api/v3 v3.2024022.1/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
|  | ||||
| @ -10,17 +10,12 @@ const CodePasswordSeparator = ";" | ||||
|  | ||||
| var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`) | ||||
|  | ||||
| // Sets the secret answers for the flow executor for protocols that only support username/password | ||||
| // according to used options | ||||
| func (fe *FlowExecutor) SetSecrets(password string, mfaCodeBased bool) { | ||||
| 	if fe.Answers[StageAuthenticatorValidate] != "" || fe.Answers[StagePassword] != "" { | ||||
| 		return | ||||
| 	} | ||||
| 	fe.Answers[StagePassword] = password | ||||
| 	if !mfaCodeBased { | ||||
| 		// If code-based MFA is disabled StageAuthenticatorValidate answer is set to password. | ||||
| 		// This allows flows with a mfa stage only. | ||||
| 		fe.Answers[StageAuthenticatorValidate] = password | ||||
| // CheckPasswordInlineMFA For protocols that only support username/password, check if the password | ||||
| // contains the TOTP code | ||||
| func (fe *FlowExecutor) CheckPasswordInlineMFA() { | ||||
| 	password := fe.Answers[StagePassword] | ||||
| 	// We already have an authenticator answer | ||||
| 	if fe.Answers[StageAuthenticatorValidate] != "" { | ||||
| 		return | ||||
| 	} | ||||
| 	// password doesn't contain the separator | ||||
|  | ||||
| @ -23,7 +23,10 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul | ||||
| 	fe.Params.Add("goauthentik.io/outpost/ldap", "true") | ||||
|  | ||||
| 	fe.Answers[flow.StageIdentification] = username | ||||
| 	fe.SetSecrets(req.BindPW, db.si.GetMFASupport()) | ||||
| 	fe.Answers[flow.StagePassword] = req.BindPW | ||||
| 	if db.si.GetMFASupport() { | ||||
| 		fe.CheckPasswordInlineMFA() | ||||
| 	} | ||||
|  | ||||
| 	passed, err := fe.Execute() | ||||
| 	flags := flags.UserFlags{ | ||||
|  | ||||
| @ -35,7 +35,7 @@ type ProviderInstance struct { | ||||
| 	cert                *tls.Certificate | ||||
| 	certUUID            string | ||||
| 	outpostName         string | ||||
| 	providerPk          int32 | ||||
| 	outpostPk           int32 | ||||
| 	searchAllowedGroups []*strfmt.UUID | ||||
| 	boundUsersMutex     *sync.RWMutex | ||||
| 	boundUsers          map[string]*flags.UserFlags | ||||
|  | ||||
| @ -22,7 +22,7 @@ import ( | ||||
|  | ||||
| func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance { | ||||
| 	for _, p := range ls.providers { | ||||
| 		if p.providerPk == pk { | ||||
| 		if p.outpostPk == pk { | ||||
| 			return p | ||||
| 		} | ||||
| 	} | ||||
| @ -83,7 +83,7 @@ func (ls *LDAPServer) Refresh() error { | ||||
| 			gidStartNumber:         provider.GetGidStartNumber(), | ||||
| 			mfaSupport:             provider.GetMfaSupport(), | ||||
| 			outpostName:            ls.ac.Outpost.Name, | ||||
| 			providerPk:             provider.Pk, | ||||
| 			outpostPk:              provider.Pk, | ||||
| 		} | ||||
| 		if kp := provider.Certificate.Get(); kp != nil { | ||||
| 			err := ls.cs.AddKeypair(*kp) | ||||
|  | ||||
| @ -56,7 +56,7 @@ func TestProxy_Redirect_Subdirectory(t *testing.T) { | ||||
| 	loc, _ := rr.Result().Location() | ||||
| 	assert.Equal( | ||||
| 		t, | ||||
| 		"https://ext.t.goauthentik.io/subdir/outpost.goauthentik.io/start?rd=https%3A%2F%2Fext.t.goauthentik.io%2Fsubdir%2Ffoo", | ||||
| 		"https://ext.t.goauthentik.io/subdir/outpost.goauthentik.io/start?rd=https%3A%2F%2Fext.t.goauthentik.io%2Ffoo", | ||||
| 		loc.String(), | ||||
| 	) | ||||
| } | ||||
|  | ||||
| @ -32,8 +32,8 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | ||||
| 	} | ||||
| 	if a.isEmbedded { | ||||
| 		client := redis.NewClient(&redis.Options{ | ||||
| 			Addr:     fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), | ||||
| 			Username: config.Get().Redis.Username, | ||||
| 			Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), | ||||
| 			// Username: config.Get().Redis.Password, | ||||
| 			Password: config.Get().Redis.Password, | ||||
| 			DB:       config.Get().Redis.DB, | ||||
| 		}) | ||||
|  | ||||
| @ -3,6 +3,7 @@ package application | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| @ -10,12 +11,22 @@ import ( | ||||
| 	"goauthentik.io/internal/outpost/proxyv2/constants" | ||||
| ) | ||||
|  | ||||
| func urlJoin(originalUrl string, newPath string) string { | ||||
| 	u, err := url.JoinPath(originalUrl, newPath) | ||||
| func urlPathSet(originalUrl string, newPath string) string { | ||||
| 	u, err := url.Parse(originalUrl) | ||||
| 	if err != nil { | ||||
| 		return originalUrl | ||||
| 	} | ||||
| 	return u | ||||
| 	u.Path = newPath | ||||
| 	return u.String() | ||||
| } | ||||
|  | ||||
| func urlJoin(originalUrl string, newPath string) string { | ||||
| 	u, err := url.Parse(originalUrl) | ||||
| 	if err != nil { | ||||
| 		return originalUrl | ||||
| 	} | ||||
| 	u.Path = path.Join(u.Path, newPath) | ||||
| 	return u.String() | ||||
| } | ||||
|  | ||||
| func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | ||||
| @ -35,7 +46,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path) | ||||
| 	redirectUrl := urlPathSet(a.proxyConfig.ExternalHost, r.URL.Path) | ||||
|  | ||||
| 	if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN { | ||||
| 		dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".") | ||||
|  | ||||
| @ -6,10 +6,8 @@ import ( | ||||
| 	"net" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/outpost/ldap/flags" | ||||
| ) | ||||
|  | ||||
| func parseCIDRs(raw string) []*net.IPNet { | ||||
| @ -31,25 +29,6 @@ func parseCIDRs(raw string) []*net.IPNet { | ||||
| 	return cidrs | ||||
| } | ||||
|  | ||||
| func (rs *RadiusServer) getCurrentProvider(pk int32) *ProviderInstance { | ||||
| 	for _, p := range rs.providers { | ||||
| 		if p.providerPk == pk { | ||||
| 			return p | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (rs *RadiusServer) getInvalidationFlow() string { | ||||
| 	req, _, err := rs.ac.Client.CoreApi.CoreBrandsCurrentRetrieve(context.Background()).Execute() | ||||
| 	if err != nil { | ||||
| 		rs.log.WithError(err).Warning("failed to fetch brand config") | ||||
| 		return "" | ||||
| 	} | ||||
| 	flow := req.GetFlowInvalidation() | ||||
| 	return flow | ||||
| } | ||||
|  | ||||
| func (rs *RadiusServer) Refresh() error { | ||||
| 	outposts, _, err := rs.ac.Client.OutpostsApi.OutpostsRadiusList(context.Background()).Execute() | ||||
| 	if err != nil { | ||||
| @ -58,33 +37,17 @@ func (rs *RadiusServer) Refresh() error { | ||||
| 	if len(outposts.Results) < 1 { | ||||
| 		return errors.New("no radius provider defined") | ||||
| 	} | ||||
| 	invalidationFlow := rs.getInvalidationFlow() | ||||
| 	providers := make([]*ProviderInstance, len(outposts.Results)) | ||||
| 	for idx, provider := range outposts.Results { | ||||
| 		logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name) | ||||
|  | ||||
| 		// Get existing instance so we can transfer boundUsers | ||||
| 		existing := rs.getCurrentProvider(provider.Pk) | ||||
| 		usersMutex := &sync.RWMutex{} | ||||
| 		users := make(map[string]*flags.UserFlags) | ||||
| 		if existing != nil { | ||||
| 			usersMutex = existing.boundUsersMutex | ||||
| 			// Shallow copy, no need to lock | ||||
| 			users = existing.boundUsers | ||||
| 		} | ||||
|  | ||||
| 		providers[idx] = &ProviderInstance{ | ||||
| 			SharedSecret:           []byte(provider.GetSharedSecret()), | ||||
| 			ClientNetworks:         parseCIDRs(provider.GetClientNetworks()), | ||||
| 			MFASupport:             provider.GetMfaSupport(), | ||||
| 			appSlug:                provider.ApplicationSlug, | ||||
| 			authenticationFlowSlug: provider.AuthFlowSlug, | ||||
| 			invalidationFlowSlug:   invalidationFlow, | ||||
| 			s:                      rs, | ||||
| 			log:                    logger, | ||||
| 			providerPk:             provider.Pk, | ||||
| 			boundUsersMutex:        usersMutex, | ||||
| 			boundUsers:             users, | ||||
| 			SharedSecret:   []byte(provider.GetSharedSecret()), | ||||
| 			ClientNetworks: parseCIDRs(provider.GetClientNetworks()), | ||||
| 			MFASupport:     provider.GetMfaSupport(), | ||||
| 			appSlug:        provider.ApplicationSlug, | ||||
| 			flowSlug:       provider.AuthFlowSlug, | ||||
| 			s:              rs, | ||||
| 			log:            logger, | ||||
| 		} | ||||
| 	} | ||||
| 	rs.providers = providers | ||||
|  | ||||
| @ -4,17 +4,15 @@ import ( | ||||
| 	"github.com/prometheus/client_golang/prometheus" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/outpost/flow" | ||||
| 	"goauthentik.io/internal/outpost/ldap/flags" | ||||
| 	"goauthentik.io/internal/outpost/radius/metrics" | ||||
| 	"layeh.com/radius" | ||||
| 	"layeh.com/radius/rfc2865" | ||||
| 	"layeh.com/radius/rfc2866" | ||||
| ) | ||||
|  | ||||
| func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) { | ||||
| 	username := rfc2865.UserName_GetString(r.Packet) | ||||
|  | ||||
| 	fe := flow.NewFlowExecutor(r.Context(), r.pi.authenticationFlowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{ | ||||
| 	fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{ | ||||
| 		"username":  username, | ||||
| 		"client":    r.RemoteAddr(), | ||||
| 		"requestId": r.ID(), | ||||
| @ -23,7 +21,10 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR | ||||
| 	fe.Params.Add("goauthentik.io/outpost/radius", "true") | ||||
|  | ||||
| 	fe.Answers[flow.StageIdentification] = username | ||||
| 	fe.SetSecrets(rfc2865.UserPassword_GetString(r.Packet), r.pi.MFASupport) | ||||
| 	fe.Answers[flow.StagePassword] = rfc2865.UserPassword_GetString(r.Packet) | ||||
| 	if r.pi.MFASupport { | ||||
| 		fe.CheckPasswordInlineMFA() | ||||
| 	} | ||||
|  | ||||
| 	passed, err := fe.Execute() | ||||
| 	if err != nil { | ||||
| @ -66,28 +67,5 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR | ||||
| 		}).Inc() | ||||
| 		return | ||||
| 	} | ||||
| 	// Get user info to store in context | ||||
| 	userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(r.Context()).Execute() | ||||
| 	if err != nil { | ||||
| 		metrics.RequestsRejected.With(prometheus.Labels{ | ||||
| 			"outpost_name": rs.ac.Outpost.Name, | ||||
| 			"type":         "bind", | ||||
| 			"reason":       "user_info_fail", | ||||
| 		}).Inc() | ||||
| 		r.Log().WithError(err).Warning("failed to get user info") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	response := r.Response(radius.CodeAccessAccept) | ||||
| 	_ = rfc2866.AcctSessionID_SetString(response, fe.GetSession().String()) | ||||
| 	r.pi.boundUsersMutex.Lock() | ||||
| 	r.pi.boundUsers[fe.GetSession().String()] = &flags.UserFlags{ | ||||
| 		Session: fe.GetSession(), | ||||
| 		UserPk:  userInfo.Original.Pk, | ||||
| 	} | ||||
| 	r.pi.boundUsersMutex.Unlock() | ||||
| 	err = w.Write(response) | ||||
| 	if err != nil { | ||||
| 		r.Log().WithError(err).Warning("failed to write response") | ||||
| 	} | ||||
| 	_ = w.Write(r.Response(radius.CodeAccessAccept)) | ||||
| } | ||||
|  | ||||
| @ -1,54 +0,0 @@ | ||||
| package radius | ||||
|  | ||||
| import ( | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/outpost/flow" | ||||
| 	"goauthentik.io/internal/outpost/ldap/flags" | ||||
| 	"layeh.com/radius" | ||||
| 	"layeh.com/radius/rfc2866" | ||||
| ) | ||||
|  | ||||
| func (rs *RadiusServer) Handle_DisconnectRequest(w radius.ResponseWriter, r *RadiusRequest) { | ||||
| 	session := rfc2866.AcctSessionID_GetString(r.Packet) | ||||
|  | ||||
| 	sendFailResponse := func() { | ||||
| 		failResponse := r.Response(radius.CodeDisconnectACK) | ||||
| 		err := w.Write(failResponse) | ||||
| 		if err != nil { | ||||
| 			r.Log().WithError(err).Warning("failed to write response") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r.pi.boundUsersMutex.Lock() | ||||
| 	var f *flags.UserFlags | ||||
| 	if ff, ok := r.pi.boundUsers[session]; !ok { | ||||
| 		r.pi.boundUsersMutex.Unlock() | ||||
| 		sendFailResponse() | ||||
| 		return | ||||
| 	} else { | ||||
| 		f = ff | ||||
| 	} | ||||
| 	r.pi.boundUsersMutex.Unlock() | ||||
|  | ||||
| 	fe := flow.NewFlowExecutor(r.Context(), r.pi.invalidationFlowSlug, rs.ac.Client.GetConfig(), log.Fields{ | ||||
| 		"client":    r.RemoteAddr(), | ||||
| 		"requestId": r.ID(), | ||||
| 	}) | ||||
| 	fe.SetSession(f.Session) | ||||
| 	fe.DelegateClientIP(r.RemoteAddr()) | ||||
| 	fe.Params.Add("goauthentik.io/outpost/radius", "true") | ||||
| 	_, err := fe.Execute() | ||||
| 	if err != nil { | ||||
| 		r.log.WithError(err).Warning("failed to logout user") | ||||
| 		sendFailResponse() | ||||
| 		return | ||||
| 	} | ||||
| 	r.pi.boundUsersMutex.Lock() | ||||
| 	delete(r.pi.boundUsers, session) | ||||
| 	r.pi.boundUsersMutex.Unlock() | ||||
| 	response := r.Response(radius.CodeDisconnectACK) | ||||
| 	err = w.Write(response) | ||||
| 	if err != nil { | ||||
| 		r.Log().WithError(err).Warning("failed to write response") | ||||
| 	} | ||||
| } | ||||
| @ -74,12 +74,7 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request) | ||||
| 	} | ||||
| 	nr.pi = pi | ||||
|  | ||||
| 	switch nr.Code { | ||||
| 	case radius.CodeAccessRequest: | ||||
| 	if nr.Code == radius.CodeAccessRequest { | ||||
| 		rs.Handle_AccessRequest(w, nr) | ||||
| 	case radius.CodeDisconnectRequest: | ||||
| 		rs.Handle_DisconnectRequest(w, nr) | ||||
| 	default: | ||||
| 		nr.Log().WithField("code", nr.Code.String()).Debug("Unsupported packet code") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -9,25 +9,20 @@ import ( | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/internal/config" | ||||
| 	"goauthentik.io/internal/outpost/ak" | ||||
| 	"goauthentik.io/internal/outpost/ldap/flags" | ||||
| 	"goauthentik.io/internal/outpost/radius/metrics" | ||||
|  | ||||
| 	"layeh.com/radius" | ||||
| ) | ||||
|  | ||||
| type ProviderInstance struct { | ||||
| 	ClientNetworks  []*net.IPNet | ||||
| 	SharedSecret    []byte | ||||
| 	MFASupport      bool | ||||
| 	boundUsersMutex *sync.RWMutex | ||||
| 	boundUsers      map[string]*flags.UserFlags | ||||
| 	providerPk      int32 | ||||
| 	ClientNetworks []*net.IPNet | ||||
| 	SharedSecret   []byte | ||||
| 	MFASupport     bool | ||||
|  | ||||
| 	appSlug                string | ||||
| 	authenticationFlowSlug string | ||||
| 	invalidationFlowSlug   string | ||||
| 	s                      *RadiusServer | ||||
| 	log                    *log.Entry | ||||
| 	appSlug  string | ||||
| 	flowSlug string | ||||
| 	s        *RadiusServer | ||||
| 	log      *log.Entry | ||||
| } | ||||
|  | ||||
| type RadiusServer struct { | ||||
|  | ||||
| @ -14,27 +14,26 @@ import ( | ||||
| ) | ||||
|  | ||||
| func (ws *WebServer) configureStatic() { | ||||
| 	staticRouter := ws.lh.NewRoute().Subrouter() | ||||
| 	staticRouter.Use(ws.staticHeaderMiddleware) | ||||
| 	staticRouter.Use(web.DisableIndex) | ||||
|  | ||||
| 	statRouter := ws.lh.NewRoute().Subrouter() | ||||
| 	statRouter.Use(ws.staticHeaderMiddleware) | ||||
| 	indexLessRouter := statRouter.NewRoute().Subrouter() | ||||
| 	indexLessRouter.Use(web.DisableIndex) | ||||
| 	distFs := http.FileServer(http.Dir("./web/dist")) | ||||
| 	distHandler := http.StripPrefix("/static/dist/", distFs) | ||||
| 	authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik"))) | ||||
| 	helpHandler := http.FileServer(http.Dir("./website/help/")) | ||||
| 	indexLessRouter.PathPrefix("/static/dist/").Handler(distHandler) | ||||
| 	indexLessRouter.PathPrefix("/static/authentik/").Handler(authentikHandler) | ||||
|  | ||||
| 	// Root file paths, from which they should be accessed | ||||
| 	staticRouter.PathPrefix("/static/dist/").Handler(http.StripPrefix("/static/dist/", distFs)) | ||||
| 	staticRouter.PathPrefix("/static/authentik/").Handler(authentikHandler) | ||||
|  | ||||
| 	// Also serve assets folder in specific interfaces since fonts in patternfly are imported | ||||
| 	// with a relative path | ||||
| 	staticRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 	// Prevent font-loading issues on safari, which loads fonts relatively to the URL the browser is on | ||||
| 	indexLessRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		vars := mux.Vars(r) | ||||
|  | ||||
| 		web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r) | ||||
| 	}) | ||||
| 	staticRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs)) | ||||
| 	staticRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs)) | ||||
| 	staticRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 	indexLessRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs)) | ||||
| 	indexLessRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs)) | ||||
| 	indexLessRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		vars := mux.Vars(r) | ||||
|  | ||||
| 		web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r) | ||||
| @ -43,13 +42,12 @@ func (ws *WebServer) configureStatic() { | ||||
| 	// Media files, if backend is file | ||||
| 	if config.Get().Storage.Media.Backend == "file" { | ||||
| 		fsMedia := http.FileServer(http.Dir(config.Get().Storage.Media.File.Path)) | ||||
| 		staticRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fsMedia)) | ||||
| 		indexLessRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fsMedia)) | ||||
| 	} | ||||
|  | ||||
| 	staticRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", http.FileServer(http.Dir("./website/help/")))) | ||||
| 	staticRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently)) | ||||
| 	statRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", helpHandler)) | ||||
| 	statRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently)) | ||||
|  | ||||
| 	// Static misc files | ||||
| 	ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		rw.Header()["Content-Type"] = []string{"text/plain"} | ||||
| 		rw.WriteHeader(200) | ||||
|  | ||||
| @ -64,7 +64,6 @@ def release_lock(cursor: Cursor): | ||||
|     """Release database lock""" | ||||
|     if not LOCKED: | ||||
|         return | ||||
|     LOGGER.info("releasing database lock") | ||||
|     cursor.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,)) | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,12 +0,0 @@ | ||||
| from lifecycle.migrate import BaseMigration | ||||
|  | ||||
|  | ||||
| class Migration(BaseMigration): | ||||
|     def needs_migration(self) -> bool: | ||||
|         self.cur.execute( | ||||
|             "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'template';" | ||||
|         ) | ||||
|         return not bool(self.cur.rowcount) | ||||
|  | ||||
|     def run(self): | ||||
|         self.cur.execute("CREATE SCHEMA IF NOT EXISTS template; COMMIT;") | ||||
| @ -116,7 +116,6 @@ class Migration(BaseMigration): | ||||
|                     host=CONFIG.get("redis.host"), | ||||
|                     port=6379, | ||||
|                     db=db, | ||||
|                     username=CONFIG.get("redis.username"), | ||||
|                     password=CONFIG.get("redis.password"), | ||||
|                 ) | ||||
|                 redis.flushall() | ||||
|  | ||||
| @ -19,7 +19,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2024-03-05 00:07+0000\n" | ||||
| "POT-Creation-Date: 2024-03-01 00:07+0000\n" | ||||
| "PO-Revision-Date: 2022-09-26 16:47+0000\n" | ||||
| "Last-Translator: Marc Schmitt, 2024\n" | ||||
| "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" | ||||
| @ -802,10 +802,6 @@ msgstr "Jeton du flux" | ||||
| msgid "Flow Tokens" | ||||
| msgstr "Jetons du flux" | ||||
|  | ||||
| #: authentik/flows/views/executor.py | ||||
| msgid "Invalid next URL" | ||||
| msgstr "URL suivante invalide" | ||||
|  | ||||
| #: authentik/lib/utils/time.py | ||||
| #, python-format | ||||
| msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'." | ||||
|  | ||||
							
								
								
									
										305
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										305
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. | ||||
| # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. | ||||
|  | ||||
| [[package]] | ||||
| name = "aiohttp" | ||||
| @ -392,33 +392,33 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "black" | ||||
| version = "24.3.0" | ||||
| version = "24.2.0" | ||||
| description = "The uncompromising code formatter." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, | ||||
|     {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, | ||||
|     {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, | ||||
|     {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, | ||||
|     {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, | ||||
|     {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, | ||||
|     {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, | ||||
|     {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, | ||||
|     {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, | ||||
|     {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, | ||||
|     {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, | ||||
|     {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, | ||||
|     {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, | ||||
|     {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, | ||||
|     {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, | ||||
|     {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, | ||||
|     {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, | ||||
|     {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, | ||||
|     {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, | ||||
|     {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, | ||||
|     {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, | ||||
|     {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, | ||||
|     {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"}, | ||||
|     {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"}, | ||||
|     {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"}, | ||||
|     {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"}, | ||||
|     {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"}, | ||||
|     {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"}, | ||||
|     {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"}, | ||||
|     {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"}, | ||||
|     {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"}, | ||||
|     {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"}, | ||||
|     {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"}, | ||||
|     {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"}, | ||||
|     {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"}, | ||||
|     {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"}, | ||||
|     {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"}, | ||||
|     {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"}, | ||||
|     {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"}, | ||||
|     {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"}, | ||||
|     {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"}, | ||||
|     {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"}, | ||||
|     {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"}, | ||||
|     {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -455,19 +455,19 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "botocore" | ||||
| version = "1.34.63" | ||||
| version = "1.34.15" | ||||
| description = "Low-level, data-driven core of boto 3." | ||||
| optional = false | ||||
| python-versions = ">= 3.8" | ||||
| files = [ | ||||
|     {file = "botocore-1.34.63-py3-none-any.whl", hash = "sha256:8a6cbc3a5c5988725c00815f8f7f6baf81980b19d9a2ee414b031e726759dba9"}, | ||||
|     {file = "botocore-1.34.63.tar.gz", hash = "sha256:2237743fc3ed68319bc358b451e7c13a02110242b1522b839806fd64fcee45fb"}, | ||||
|     {file = "botocore-1.34.15-py3-none-any.whl", hash = "sha256:16bcf871e67ef0177593f06e9e5bae4db51c9a9a2e953cb14feeb42d53441a85"}, | ||||
|     {file = "botocore-1.34.15.tar.gz", hash = "sha256:c3c3404962a6d9d5e1634bd70ed53b8eff1ff17ee9d7a6240e9e8c94db48ad6f"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| jmespath = ">=0.7.1,<2.0.0" | ||||
| python-dateutil = ">=2.1,<3.0.0" | ||||
| urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} | ||||
| urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} | ||||
|  | ||||
| [package.extras] | ||||
| crt = ["awscrt (==0.19.19)"] | ||||
| @ -919,63 +919,63 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "coverage" | ||||
| version = "7.4.4" | ||||
| version = "7.4.3" | ||||
| description = "Code coverage measurement for Python" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, | ||||
|     {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, | ||||
|     {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, | ||||
|     {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, | ||||
|     {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, | ||||
|     {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, | ||||
|     {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, | ||||
|     {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, | ||||
|     {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, | ||||
|     {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, | ||||
|     {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, | ||||
|     {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, | ||||
|     {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, | ||||
|     {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, | ||||
|     {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, | ||||
| ] | ||||
|  | ||||
| [package.extras] | ||||
| @ -1142,13 +1142,13 @@ bcrypt = ["bcrypt"] | ||||
|  | ||||
| [[package]] | ||||
| name = "django-filter" | ||||
| version = "24.2" | ||||
| version = "24.1" | ||||
| description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"}, | ||||
|     {file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"}, | ||||
|     {file = "django-filter-24.1.tar.gz", hash = "sha256:65cb43ce272077e5ac6aae1054d76c121cd6b552e296a82a13921e9371baf8c1"}, | ||||
|     {file = "django_filter-24.1-py3-none-any.whl", hash = "sha256:335bcae6cbd3e984b024841070f567b22faea57594f27d37c52f8f131f8d8621"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1389,13 +1389,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "duo-client" | ||||
| version = "5.3.0" | ||||
| version = "5.2.0" | ||||
| description = "Reference client for Duo Security APIs" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "duo_client-5.3.0-py3-none-any.whl", hash = "sha256:85614bb684cef96285268aef0c1e858df939f6e8a190fb2c707d700bb0215766"}, | ||||
|     {file = "duo_client-5.3.0.tar.gz", hash = "sha256:afa5ef98a42f06965a2702ca41dba9c85c483abd945e0a440f0ec4871b7593bf"}, | ||||
|     {file = "duo_client-5.2.0-py3-none-any.whl", hash = "sha256:da3237e34300665c40ba5215f1e6656fec1a0136295917541aa973e7fcbf027e"}, | ||||
|     {file = "duo_client-5.2.0.tar.gz", hash = "sha256:f82361740792b06303f9721e7ba593916080461769396b4f73c0502c0bfcee44"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1712,13 +1712,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "importlib-metadata" | ||||
| version = "7.1.0" | ||||
| version = "7.0.2" | ||||
| description = "Read metadata from Python packages" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, | ||||
|     {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, | ||||
|     {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, | ||||
|     {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -1727,7 +1727,7 @@ zipp = ">=0.5" | ||||
| [package.extras] | ||||
| docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] | ||||
| perf = ["ipython"] | ||||
| testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] | ||||
| testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "incremental" | ||||
| @ -3332,30 +3332,32 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "requests-mock" | ||||
| version = "1.12.1" | ||||
| version = "1.11.0" | ||||
| description = "Mock out responses from the requests package" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, | ||||
|     {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, | ||||
|     {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, | ||||
|     {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| requests = ">=2.22,<3" | ||||
| requests = ">=2.3,<3" | ||||
| six = "*" | ||||
|  | ||||
| [package.extras] | ||||
| fixture = ["fixtures"] | ||||
| test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] | ||||
|  | ||||
| [[package]] | ||||
| name = "requests-oauthlib" | ||||
| version = "2.0.0" | ||||
| version = "1.4.0" | ||||
| description = "OAuthlib authentication support for Requests." | ||||
| optional = false | ||||
| python-versions = ">=3.4" | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| files = [ | ||||
|     {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, | ||||
|     {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, | ||||
|     {file = "requests-oauthlib-1.4.0.tar.gz", hash = "sha256:acee623221e4a39abcbb919312c8ff04bd44e7e417087fb4bd5e2a2f53d5e79a"}, | ||||
|     {file = "requests_oauthlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:7a3130d94a17520169e38db6c8d75f2c974643788465ecc2e4b36d288bf13033"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3507,28 +3509,28 @@ pyasn1 = ">=0.1.3" | ||||
|  | ||||
| [[package]] | ||||
| name = "ruff" | ||||
| version = "0.3.4" | ||||
| version = "0.3.2" | ||||
| description = "An extremely fast Python linter and code formatter, written in Rust." | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| files = [ | ||||
|     {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, | ||||
|     {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, | ||||
|     {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, | ||||
|     {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, | ||||
|     {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, | ||||
|     {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, | ||||
|     {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, | ||||
|     {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, | ||||
|     {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, | ||||
|     {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, | ||||
|     {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, | ||||
|     {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"}, | ||||
|     {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"}, | ||||
|     {file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"}, | ||||
|     {file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"}, | ||||
|     {file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"}, | ||||
|     {file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"}, | ||||
|     {file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"}, | ||||
|     {file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"}, | ||||
|     {file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"}, | ||||
|     {file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"}, | ||||
|     {file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"}, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3550,13 +3552,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "selenium" | ||||
| version = "4.19.0" | ||||
| version = "4.18.1" | ||||
| description = "" | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"}, | ||||
|     {file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"}, | ||||
|     {file = "selenium-4.18.1-py3-none-any.whl", hash = "sha256:b24a3cdd2d47c29832e81345bfcde0c12bb608738013e53c781b211b418df241"}, | ||||
|     {file = "selenium-4.18.1.tar.gz", hash = "sha256:a11f67afa8bfac6b77e148c987b33f6b14eb1cae4d352722a75de1f26e3f0ae2"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3568,13 +3570,13 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} | ||||
|  | ||||
| [[package]] | ||||
| name = "sentry-sdk" | ||||
| version = "1.44.0" | ||||
| version = "1.41.0" | ||||
| description = "Python client for Sentry (https://sentry.io)" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "sentry-sdk-1.44.0.tar.gz", hash = "sha256:f7125a9235795811962d52ff796dc032cd1d0dd98b59beaced8380371cd9c13c"}, | ||||
|     {file = "sentry_sdk-1.44.0-py2.py3-none-any.whl", hash = "sha256:eb65289da013ca92fad2694851ad2f086aa3825e808dc285bd7dcaf63602bb18"}, | ||||
|     {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"}, | ||||
|     {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3588,7 +3590,6 @@ asyncpg = ["asyncpg (>=0.23)"] | ||||
| beam = ["apache-beam (>=2.12)"] | ||||
| bottle = ["bottle (>=0.12.13)"] | ||||
| celery = ["celery (>=3)"] | ||||
| celery-redbeat = ["celery-redbeat (>=2)"] | ||||
| chalice = ["chalice (>=1.16.0)"] | ||||
| clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] | ||||
| django = ["django (>=1.8)"] | ||||
| @ -3599,7 +3600,6 @@ grpcio = ["grpcio (>=1.21.1)"] | ||||
| httpx = ["httpx (>=0.16.0)"] | ||||
| huey = ["huey (>=2)"] | ||||
| loguru = ["loguru (>=0.5)"] | ||||
| openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] | ||||
| opentelemetry = ["opentelemetry-distro (>=0.35b0)"] | ||||
| opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] | ||||
| pure-eval = ["asttokens", "executing", "pure-eval"] | ||||
| @ -3818,13 +3818,13 @@ wsproto = ">=0.14" | ||||
|  | ||||
| [[package]] | ||||
| name = "twilio" | ||||
| version = "9.0.2" | ||||
| version = "9.0.1" | ||||
| description = "Twilio API client and TwiML generator" | ||||
| optional = false | ||||
| python-versions = ">=3.7.0" | ||||
| files = [ | ||||
|     {file = "twilio-9.0.2-py2.py3-none-any.whl", hash = "sha256:db89a8326f92240cdd8dc1dafb1d3f69169576243be9b9cbf5cf7778ecab0eed"}, | ||||
|     {file = "twilio-9.0.2.tar.gz", hash = "sha256:9450b7a9987c32146d1760c8680f92baa76b4ba570543758a4937da48ae46d77"}, | ||||
|     {file = "twilio-9.0.1-py2.py3-none-any.whl", hash = "sha256:7df45f314140b5931199420a2a00f0466d2fd16aff2f8c3e1589a47adc9deecb"}, | ||||
|     {file = "twilio-9.0.1.tar.gz", hash = "sha256:4ffb63342dff9a5b24dd3f8d33e8fac2ceb55dcd10936d50456cbb44e1834e72"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -3962,33 +3962,48 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "urllib3" | ||||
| version = "2.2.1" | ||||
| version = "1.26.18" | ||||
| description = "HTTP library with thread-safe connection pooling, file post, and more." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" | ||||
| files = [ | ||||
|     {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, | ||||
|     {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, | ||||
|     {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, | ||||
|     {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} | ||||
| certifi = {version = "*", optional = true, markers = "extra == \"secure\""} | ||||
| cryptography = {version = ">=1.3.4", optional = true, markers = "extra == \"secure\""} | ||||
| idna = {version = ">=2.0.0", optional = true, markers = "extra == \"secure\""} | ||||
| pyOpenSSL = {version = ">=0.14", optional = true, markers = "extra == \"secure\""} | ||||
| PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} | ||||
| urllib3-secure-extra = {version = "*", optional = true, markers = "extra == \"secure\""} | ||||
|  | ||||
| [package.extras] | ||||
| brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] | ||||
| h2 = ["h2 (>=4,<5)"] | ||||
| socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] | ||||
| zstd = ["zstandard (>=0.18.0)"] | ||||
| brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] | ||||
| secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] | ||||
| socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "urllib3-secure-extra" | ||||
| version = "0.1.0" | ||||
| description = "Marker library to detect whether urllib3 was installed with the deprecated [secure] extra" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "urllib3-secure-extra-0.1.0.tar.gz", hash = "sha256:ee9409cbfeb4b8609047be4c32fb4317870c602767e53fd8a41005ebe6a41dff"}, | ||||
|     {file = "urllib3_secure_extra-0.1.0-py2.py3-none-any.whl", hash = "sha256:f7adcb108b4d12a4b26b99eb60e265d087f435052a76aefa396b6ee85e9a6ef9"}, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "uvicorn" | ||||
| version = "0.29.0" | ||||
| version = "0.28.0" | ||||
| description = "The lightning-fast ASGI server." | ||||
| optional = false | ||||
| python-versions = ">=3.8" | ||||
| files = [ | ||||
|     {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, | ||||
|     {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, | ||||
|     {file = "uvicorn-0.28.0-py3-none-any.whl", hash = "sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1"}, | ||||
|     {file = "uvicorn-0.28.0.tar.gz", hash = "sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4201,13 +4216,13 @@ files = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "webauthn" | ||||
| version = "2.1.0" | ||||
| version = "2.0.0" | ||||
| description = "Pythonic WebAuthn" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| files = [ | ||||
|     {file = "webauthn-2.1.0-py3-none-any.whl", hash = "sha256:9e1cf916e5ed7c01d54a6dfcc19dacbd2b87b81d2648f001b1fcbcb7aa2ff130"}, | ||||
|     {file = "webauthn-2.1.0.tar.gz", hash = "sha256:b196a4246c2818820857ba195c6e6e5398c761117f2269e3d2deab11c7995fc4"}, | ||||
|     {file = "webauthn-2.0.0-py3-none-any.whl", hash = "sha256:644dc68af5caaade06be6a2a2278775e85116e92dd755ad7a49d992d51c82033"}, | ||||
|     {file = "webauthn-2.0.0.tar.gz", hash = "sha256:12cc1759da98668b8242badc37c4129df300f89d89f5c183fac80e7b33c41dfd"}, | ||||
| ] | ||||
|  | ||||
| [package.dependencies] | ||||
| @ -4536,4 +4551,4 @@ files = [ | ||||
| [metadata] | ||||
| lock-version = "2.0" | ||||
| python-versions = "~3.12" | ||||
| content-hash = "04ef13e2692c158e5eda1e89876a215746c56a891a161d8434d808dc12c0fc7a" | ||||
| content-hash = "825f1d552ba34206f7bfd55b70bfb42bc5d769605f59410703828ae787cd0baf" | ||||
|  | ||||
| @ -140,7 +140,7 @@ twilio = "*" | ||||
| twisted = "*" | ||||
| ua-parser = "*" | ||||
| # Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937 | ||||
| urllib3 = { extras = ["secure"], version = "<3" } | ||||
| urllib3 = { extras = ["secure"], version = "<2" } | ||||
| uvicorn = { extras = ["standard"], version = "*" } | ||||
| watchdog = "*" | ||||
| webauthn = "*" | ||||
|  | ||||
							
								
								
									
										493
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										493
									
								
								schema.yml
									
									
									
									
									
								
							| @ -18512,7 +18512,6 @@ paths: | ||||
|           - authentik_stages_password.passwordstage | ||||
|           - authentik_stages_prompt.prompt | ||||
|           - authentik_stages_prompt.promptstage | ||||
|           - authentik_stages_source.sourcestage | ||||
|           - authentik_stages_user_delete.userdeletestage | ||||
|           - authentik_stages_user_login.userloginstage | ||||
|           - authentik_stages_user_logout.userlogoutstage | ||||
| @ -18588,7 +18587,6 @@ paths: | ||||
|           * `authentik_providers_rac.racprovider` - RAC Provider | ||||
|           * `authentik_providers_rac.endpoint` - RAC Endpoint | ||||
|           * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping | ||||
|           * `authentik_stages_source.sourcestage` - Source Stage | ||||
|           * `authentik_events.event` - Event | ||||
|           * `authentik_events.notificationtransport` - Notification Transport | ||||
|           * `authentik_events.notification` - Notification | ||||
| @ -18802,7 +18800,6 @@ paths: | ||||
|           - authentik_stages_password.passwordstage | ||||
|           - authentik_stages_prompt.prompt | ||||
|           - authentik_stages_prompt.promptstage | ||||
|           - authentik_stages_source.sourcestage | ||||
|           - authentik_stages_user_delete.userdeletestage | ||||
|           - authentik_stages_user_login.userloginstage | ||||
|           - authentik_stages_user_logout.userlogoutstage | ||||
| @ -18878,7 +18875,6 @@ paths: | ||||
|           * `authentik_providers_rac.racprovider` - RAC Provider | ||||
|           * `authentik_providers_rac.endpoint` - RAC Endpoint | ||||
|           * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping | ||||
|           * `authentik_stages_source.sourcestage` - Source Stage | ||||
|           * `authentik_events.event` - Event | ||||
|           * `authentik_events.notificationtransport` - Notification Transport | ||||
|           * `authentik_events.notification` - Notification | ||||
| @ -19926,10 +19922,6 @@ paths: | ||||
|         description: Number of results to return per page. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - in: query | ||||
|         name: password_login_update_internal_password | ||||
|         schema: | ||||
|           type: boolean | ||||
|       - in: query | ||||
|         name: peer_certificate | ||||
|         schema: | ||||
| @ -27880,289 +27872,6 @@ paths: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /stages/source/: | ||||
|     get: | ||||
|       operationId: stages_source_list | ||||
|       description: SourceStage Viewset | ||||
|       parameters: | ||||
|       - in: query | ||||
|         name: name | ||||
|         schema: | ||||
|           type: string | ||||
|       - name: ordering | ||||
|         required: false | ||||
|         in: query | ||||
|         description: Which field to use when ordering the results. | ||||
|         schema: | ||||
|           type: string | ||||
|       - name: page | ||||
|         required: false | ||||
|         in: query | ||||
|         description: A page number within the paginated result set. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - name: page_size | ||||
|         required: false | ||||
|         in: query | ||||
|         description: Number of results to return per page. | ||||
|         schema: | ||||
|           type: integer | ||||
|       - in: query | ||||
|         name: resume_timeout | ||||
|         schema: | ||||
|           type: string | ||||
|       - name: search | ||||
|         required: false | ||||
|         in: query | ||||
|         description: A search term. | ||||
|         schema: | ||||
|           type: string | ||||
|       - in: query | ||||
|         name: source | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|       - in: query | ||||
|         name: stage_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|       tags: | ||||
|       - stages | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/PaginatedSourceStageList' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     post: | ||||
|       operationId: stages_source_create | ||||
|       description: SourceStage Viewset | ||||
|       tags: | ||||
|       - stages | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/SourceStageRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '201': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/SourceStage' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /stages/source/{stage_uuid}/: | ||||
|     get: | ||||
|       operationId: stages_source_retrieve | ||||
|       description: SourceStage Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: stage_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Source Stage. | ||||
|         required: true | ||||
|       tags: | ||||
|       - stages | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/SourceStage' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     put: | ||||
|       operationId: stages_source_update | ||||
|       description: SourceStage Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: stage_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Source Stage. | ||||
|         required: true | ||||
|       tags: | ||||
|       - stages | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/SourceStageRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/SourceStage' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     patch: | ||||
|       operationId: stages_source_partial_update | ||||
|       description: SourceStage Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: stage_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Source Stage. | ||||
|         required: true | ||||
|       tags: | ||||
|       - stages | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/PatchedSourceStageRequest' | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/SourceStage' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|     delete: | ||||
|       operationId: stages_source_destroy | ||||
|       description: SourceStage Viewset | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: stage_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Source Stage. | ||||
|         required: true | ||||
|       tags: | ||||
|       - stages | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '204': | ||||
|           description: No response body | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /stages/source/{stage_uuid}/used_by/: | ||||
|     get: | ||||
|       operationId: stages_source_used_by_list | ||||
|       description: Get a list of all objects that use this object | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: stage_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Source Stage. | ||||
|         required: true | ||||
|       tags: | ||||
|       - stages | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   $ref: '#/components/schemas/UsedBy' | ||||
|           description: '' | ||||
|         '400': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/ValidationError' | ||||
|           description: '' | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /stages/user_delete/: | ||||
|     get: | ||||
|       operationId: stages_user_delete_list | ||||
| @ -29933,7 +29642,6 @@ components: | ||||
|       - authentik.enterprise | ||||
|       - authentik.enterprise.audit | ||||
|       - authentik.enterprise.providers.rac | ||||
|       - authentik.enterprise.stages.source | ||||
|       - authentik.events | ||||
|       type: string | ||||
|       description: |- | ||||
| @ -29988,7 +29696,6 @@ components: | ||||
|         * `authentik.enterprise` - authentik Enterprise | ||||
|         * `authentik.enterprise.audit` - authentik Enterprise.Audit | ||||
|         * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC | ||||
|         * `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source | ||||
|         * `authentik.events` - authentik Events | ||||
|     AppleChallengeResponseRequest: | ||||
|       type: object | ||||
| @ -31786,7 +31493,8 @@ components: | ||||
|         pk: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           title: Connection token uuid | ||||
|           readOnly: true | ||||
|           title: Pbm uuid | ||||
|         provider: | ||||
|           type: integer | ||||
|         provider_obj: | ||||
| @ -31796,6 +31504,7 @@ components: | ||||
|         endpoint: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           readOnly: true | ||||
|         endpoint_obj: | ||||
|           allOf: | ||||
|           - $ref: '#/components/schemas/Endpoint' | ||||
| @ -31807,6 +31516,7 @@ components: | ||||
|       required: | ||||
|       - endpoint | ||||
|       - endpoint_obj | ||||
|       - pk | ||||
|       - provider | ||||
|       - provider_obj | ||||
|       - user | ||||
| @ -31814,17 +31524,9 @@ components: | ||||
|       type: object | ||||
|       description: ConnectionToken Serializer | ||||
|       properties: | ||||
|         pk: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           title: Connection token uuid | ||||
|         provider: | ||||
|           type: integer | ||||
|         endpoint: | ||||
|           type: string | ||||
|           format: uuid | ||||
|       required: | ||||
|       - endpoint | ||||
|       - provider | ||||
|     ConsentChallenge: | ||||
|       type: object | ||||
| @ -32355,10 +32057,7 @@ components: | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/components/schemas/ErrorDetail' | ||||
|         name: | ||||
|           type: string | ||||
|       required: | ||||
|       - name | ||||
|       - type | ||||
|     DummyChallengeResponseRequest: | ||||
|       type: object | ||||
| @ -33040,7 +32739,6 @@ components: | ||||
|             * `authentik.enterprise` - authentik Enterprise | ||||
|             * `authentik.enterprise.audit` - authentik Enterprise.Audit | ||||
|             * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC | ||||
|             * `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source | ||||
|             * `authentik.events` - authentik Events | ||||
|         model: | ||||
|           allOf: | ||||
| @ -33118,7 +32816,6 @@ components: | ||||
|             * `authentik_providers_rac.racprovider` - RAC Provider | ||||
|             * `authentik_providers_rac.endpoint` - RAC Endpoint | ||||
|             * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping | ||||
|             * `authentik_stages_source.sourcestage` - Source Stage | ||||
|             * `authentik_events.event` - Event | ||||
|             * `authentik_events.notificationtransport` - Notification Transport | ||||
|             * `authentik_events.notification` - Notification | ||||
| @ -33241,7 +32938,6 @@ components: | ||||
|             * `authentik.enterprise` - authentik Enterprise | ||||
|             * `authentik.enterprise.audit` - authentik Enterprise.Audit | ||||
|             * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC | ||||
|             * `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source | ||||
|             * `authentik.events` - authentik Events | ||||
|         model: | ||||
|           allOf: | ||||
| @ -33319,7 +33015,6 @@ components: | ||||
|             * `authentik_providers_rac.racprovider` - RAC Provider | ||||
|             * `authentik_providers_rac.endpoint` - RAC Endpoint | ||||
|             * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping | ||||
|             * `authentik_stages_source.sourcestage` - Source Stage | ||||
|             * `authentik_events.event` - Event | ||||
|             * `authentik_events.notificationtransport` - Notification Transport | ||||
|             * `authentik_events.notification` - Notification | ||||
| @ -33782,7 +33477,8 @@ components: | ||||
|         logs: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/LogEvent' | ||||
|             type: object | ||||
|             additionalProperties: {} | ||||
|           readOnly: true | ||||
|         success: | ||||
|           type: boolean | ||||
| @ -34340,8 +34036,6 @@ components: | ||||
|           type: boolean | ||||
|         application_pre: | ||||
|           type: string | ||||
|         flow_designation: | ||||
|           $ref: '#/components/schemas/FlowDesignationEnum' | ||||
|         enroll_url: | ||||
|           type: string | ||||
|         recovery_url: | ||||
| @ -34357,7 +34051,6 @@ components: | ||||
|         show_source_labels: | ||||
|           type: boolean | ||||
|       required: | ||||
|       - flow_designation | ||||
|       - password_fields | ||||
|       - primary_action | ||||
|       - show_source_labels | ||||
| @ -35228,10 +34921,6 @@ components: | ||||
|         object_uniqueness_field: | ||||
|           type: string | ||||
|           description: Field which contains a unique Identifier. | ||||
|         password_login_update_internal_password: | ||||
|           type: boolean | ||||
|           description: Update internal authentik password when login succeeds with | ||||
|             LDAP | ||||
|         sync_users: | ||||
|           type: boolean | ||||
|         sync_users_password: | ||||
| @ -35373,10 +35062,6 @@ components: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           description: Field which contains a unique Identifier. | ||||
|         password_login_update_internal_password: | ||||
|           type: boolean | ||||
|           description: Update internal authentik password when login succeeds with | ||||
|             LDAP | ||||
|         sync_users: | ||||
|           type: boolean | ||||
|         sync_users_password: | ||||
| @ -35514,48 +35199,6 @@ components: | ||||
|           type: string | ||||
|       required: | ||||
|       - link | ||||
|     LogEvent: | ||||
|       type: object | ||||
|       description: Single log message with all context logged. | ||||
|       properties: | ||||
|         timestamp: | ||||
|           type: string | ||||
|           format: date-time | ||||
|         log_level: | ||||
|           $ref: '#/components/schemas/LogLevelEnum' | ||||
|         logger: | ||||
|           type: string | ||||
|         event: | ||||
|           type: string | ||||
|         attributes: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|       required: | ||||
|       - attributes | ||||
|       - event | ||||
|       - log_level | ||||
|       - logger | ||||
|       - timestamp | ||||
|     LogLevelEnum: | ||||
|       enum: | ||||
|       - critical | ||||
|       - exception | ||||
|       - error | ||||
|       - warn | ||||
|       - warning | ||||
|       - info | ||||
|       - debug | ||||
|       - notset | ||||
|       type: string | ||||
|       description: |- | ||||
|         * `critical` - critical | ||||
|         * `exception` - exception | ||||
|         * `error` - error | ||||
|         * `warn` - warn | ||||
|         * `warning` - warning | ||||
|         * `info` - info | ||||
|         * `debug` - debug | ||||
|         * `notset` - notset | ||||
|     LoginChallengeTypes: | ||||
|       oneOf: | ||||
|       - $ref: '#/components/schemas/RedirectChallenge' | ||||
| @ -35687,7 +35330,6 @@ components: | ||||
|       - authentik_providers_rac.racprovider | ||||
|       - authentik_providers_rac.endpoint | ||||
|       - authentik_providers_rac.racpropertymapping | ||||
|       - authentik_stages_source.sourcestage | ||||
|       - authentik_events.event | ||||
|       - authentik_events.notificationtransport | ||||
|       - authentik_events.notification | ||||
| @ -35764,7 +35406,6 @@ components: | ||||
|         * `authentik_providers_rac.racprovider` - RAC Provider | ||||
|         * `authentik_providers_rac.endpoint` - RAC Endpoint | ||||
|         * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping | ||||
|         * `authentik_stages_source.sourcestage` - Source Stage | ||||
|         * `authentik_events.event` - Event | ||||
|         * `authentik_events.notificationtransport` - Notification Transport | ||||
|         * `authentik_events.notification` - Notification | ||||
| @ -37699,18 +37340,6 @@ components: | ||||
|       required: | ||||
|       - pagination | ||||
|       - results | ||||
|     PaginatedSourceStageList: | ||||
|       type: object | ||||
|       properties: | ||||
|         pagination: | ||||
|           $ref: '#/components/schemas/Pagination' | ||||
|         results: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/SourceStage' | ||||
|       required: | ||||
|       - pagination | ||||
|       - results | ||||
|     PaginatedStageList: | ||||
|       type: object | ||||
|       properties: | ||||
| @ -38647,15 +38276,8 @@ components: | ||||
|       type: object | ||||
|       description: ConnectionToken Serializer | ||||
|       properties: | ||||
|         pk: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           title: Connection token uuid | ||||
|         provider: | ||||
|           type: integer | ||||
|         endpoint: | ||||
|           type: string | ||||
|           format: uuid | ||||
|     PatchedConsentStageRequest: | ||||
|       type: object | ||||
|       description: ConsentStage Serializer | ||||
| @ -38961,7 +38583,6 @@ components: | ||||
|             * `authentik.enterprise` - authentik Enterprise | ||||
|             * `authentik.enterprise.audit` - authentik Enterprise.Audit | ||||
|             * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC | ||||
|             * `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source | ||||
|             * `authentik.events` - authentik Events | ||||
|         model: | ||||
|           allOf: | ||||
| @ -39039,7 +38660,6 @@ components: | ||||
|             * `authentik_providers_rac.racprovider` - RAC Provider | ||||
|             * `authentik_providers_rac.endpoint` - RAC Endpoint | ||||
|             * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping | ||||
|             * `authentik_stages_source.sourcestage` - Source Stage | ||||
|             * `authentik_events.event` - Event | ||||
|             * `authentik_events.notificationtransport` - Notification Transport | ||||
|             * `authentik_events.notification` - Notification | ||||
| @ -39493,10 +39113,6 @@ components: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           description: Field which contains a unique Identifier. | ||||
|         password_login_update_internal_password: | ||||
|           type: boolean | ||||
|           description: Update internal authentik password when login succeeds with | ||||
|             LDAP | ||||
|         sync_users: | ||||
|           type: boolean | ||||
|         sync_users_password: | ||||
| @ -40626,25 +40242,6 @@ components: | ||||
|         impersonation: | ||||
|           type: boolean | ||||
|           description: Globally enable/disable impersonation. | ||||
|     PatchedSourceStageRequest: | ||||
|       type: object | ||||
|       description: SourceStage Serializer | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|         flow_set: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/FlowSetRequest' | ||||
|         source: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         resume_timeout: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           description: 'Amount of time a user can take to return from the source to | ||||
|             continue the flow (Format: hours=-1;minutes=-2;seconds=-3)' | ||||
|     PatchedStaticDeviceRequest: | ||||
|       type: object | ||||
|       description: Serializer for static authenticator devices | ||||
| @ -41350,7 +40947,8 @@ components: | ||||
|         log_messages: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/LogEvent' | ||||
|             type: object | ||||
|             additionalProperties: {} | ||||
|           readOnly: true | ||||
|       required: | ||||
|       - log_messages | ||||
| @ -43988,74 +43586,6 @@ components: | ||||
|       required: | ||||
|       - name | ||||
|       - slug | ||||
|     SourceStage: | ||||
|       type: object | ||||
|       description: SourceStage Serializer | ||||
|       properties: | ||||
|         pk: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           readOnly: true | ||||
|           title: Stage uuid | ||||
|         name: | ||||
|           type: string | ||||
|         component: | ||||
|           type: string | ||||
|           description: Get object type so that we know how to edit the object | ||||
|           readOnly: true | ||||
|         verbose_name: | ||||
|           type: string | ||||
|           description: Return object's verbose_name | ||||
|           readOnly: true | ||||
|         verbose_name_plural: | ||||
|           type: string | ||||
|           description: Return object's plural verbose_name | ||||
|           readOnly: true | ||||
|         meta_model_name: | ||||
|           type: string | ||||
|           description: Return internal model name | ||||
|           readOnly: true | ||||
|         flow_set: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/FlowSet' | ||||
|         source: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         resume_timeout: | ||||
|           type: string | ||||
|           description: 'Amount of time a user can take to return from the source to | ||||
|             continue the flow (Format: hours=-1;minutes=-2;seconds=-3)' | ||||
|       required: | ||||
|       - component | ||||
|       - meta_model_name | ||||
|       - name | ||||
|       - pk | ||||
|       - source | ||||
|       - verbose_name | ||||
|       - verbose_name_plural | ||||
|     SourceStageRequest: | ||||
|       type: object | ||||
|       description: SourceStage Serializer | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|         flow_set: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/FlowSetRequest' | ||||
|         source: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         resume_timeout: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|           description: 'Amount of time a user can take to return from the source to | ||||
|             continue the flow (Format: hours=-1;minutes=-2;seconds=-3)' | ||||
|       required: | ||||
|       - name | ||||
|       - source | ||||
|     SourceType: | ||||
|       type: object | ||||
|       description: Serializer for SourceType | ||||
| @ -44364,7 +43894,7 @@ components: | ||||
|         messages: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/LogEvent' | ||||
|             type: string | ||||
|       required: | ||||
|       - description | ||||
|       - duration | ||||
| @ -45654,10 +45184,6 @@ components: | ||||
|           type: string | ||||
|           description: Get latest version from cache | ||||
|           readOnly: true | ||||
|         version_latest_valid: | ||||
|           type: boolean | ||||
|           description: Check if latest version is valid | ||||
|           readOnly: true | ||||
|         build_hash: | ||||
|           type: string | ||||
|           description: Get build hash, if version is not latest or released | ||||
| @ -45671,7 +45197,6 @@ components: | ||||
|       - outdated | ||||
|       - version_current | ||||
|       - version_latest | ||||
|       - version_latest_valid | ||||
|     WebAuthnDevice: | ||||
|       type: object | ||||
|       description: Serializer for WebAuthn authenticator devices | ||||
|  | ||||
| @ -128,7 +128,6 @@ class TestSourceLDAPSamba(SeleniumTestCase): | ||||
|             base_dn="dc=test,dc=goauthentik,dc=io", | ||||
|             additional_user_dn="ou=users", | ||||
|             additional_group_dn="ou=groups", | ||||
|             password_login_update_internal_password=True, | ||||
|         ) | ||||
|         source.property_mappings.set( | ||||
|             LDAPPropertyMapping.objects.filter( | ||||
|  | ||||
| @ -10,7 +10,6 @@ from kubernetes.client.exceptions import OpenApiException | ||||
| from authentik.core.tests.utils import create_test_flow | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler | ||||
| from authentik.outposts.controllers.k8s.service import ServiceReconciler | ||||
| from authentik.outposts.controllers.k8s.triggers import NeedsUpdate | ||||
| from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType | ||||
| from authentik.outposts.tasks import outpost_connection_discovery | ||||
| @ -92,35 +91,6 @@ class OutpostKubernetesTests(TestCase): | ||||
|  | ||||
|         deployment_reconciler.delete(deployment_reconciler.get_reference_object()) | ||||
|  | ||||
|     @pytest.mark.timeout(120) | ||||
|     def test_service_reconciler(self): | ||||
|         """test that service requires update""" | ||||
|         controller = ProxyKubernetesController(self.outpost, self.service_connection) | ||||
|         service_reconciler = ServiceReconciler(controller) | ||||
|  | ||||
|         self.assertIsNotNone(service_reconciler.retrieve()) | ||||
|  | ||||
|         config = self.outpost.config | ||||
|         config.kubernetes_service_type = "NodePort" | ||||
|         config.kubernetes_json_patches = { | ||||
|             "service": [ | ||||
|                 { | ||||
|                     "op": "add", | ||||
|                     "path": "/spec/ipFamilyPolicy", | ||||
|                     "value": "PreferDualStack", | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|         self.outpost.config = config | ||||
|  | ||||
|         with self.assertRaises(NeedsUpdate): | ||||
|             service_reconciler.reconcile( | ||||
|                 service_reconciler.retrieve(), | ||||
|                 service_reconciler.get_reference_object(), | ||||
|             ) | ||||
|  | ||||
|         service_reconciler.delete(service_reconciler.get_reference_object()) | ||||
|  | ||||
|     @pytest.mark.timeout(120) | ||||
|     def test_controller_rename(self): | ||||
|         """test that objects get deleted and re-created with new names""" | ||||
|  | ||||
| @ -26,6 +26,12 @@ class TestProxyKubernetes(TestCase): | ||||
|         outpost_connection_discovery() | ||||
|         self.controller = None | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         if self.controller: | ||||
|             for log in self.controller.down_with_logs(): | ||||
|                 LOGGER.info(log) | ||||
|         return super().tearDown() | ||||
|  | ||||
|     @pytest.mark.timeout(120) | ||||
|     def test_kubernetes_controller_static(self): | ||||
|         """Test Kubernetes Controller""" | ||||
|  | ||||
							
								
								
									
										269
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										269
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -6,23 +6,23 @@ | ||||
|         "": { | ||||
|             "name": "@goauthentik/web-tests", | ||||
|             "dependencies": { | ||||
|                 "chromedriver": "^123.0.1" | ||||
|                 "chromedriver": "^122.0.6" | ||||
|             }, | ||||
|             "devDependencies": { | ||||
|                 "@trivago/prettier-plugin-sort-imports": "^4.3.0", | ||||
|                 "@typescript-eslint/eslint-plugin": "^7.4.0", | ||||
|                 "@typescript-eslint/parser": "^7.4.0", | ||||
|                 "@wdio/cli": "^8.35.1", | ||||
|                 "@wdio/local-runner": "^8.35.1", | ||||
|                 "@wdio/mocha-framework": "^8.35.0", | ||||
|                 "@typescript-eslint/eslint-plugin": "^7.2.0", | ||||
|                 "@typescript-eslint/parser": "^7.2.0", | ||||
|                 "@wdio/cli": "^8.33.1", | ||||
|                 "@wdio/local-runner": "^8.33.1", | ||||
|                 "@wdio/mocha-framework": "^8.33.1", | ||||
|                 "@wdio/spec-reporter": "^8.32.4", | ||||
|                 "eslint": "^8.57.0", | ||||
|                 "eslint-config-google": "^0.14.0", | ||||
|                 "eslint-plugin-sonarjs": "^0.25.0", | ||||
|                 "eslint-plugin-sonarjs": "^0.24.0", | ||||
|                 "npm-run-all": "^4.1.5", | ||||
|                 "prettier": "^3.2.5", | ||||
|                 "ts-node": "^10.9.2", | ||||
|                 "typescript": "^5.4.3", | ||||
|                 "typescript": "^5.4.2", | ||||
|                 "wdio-wait-for": "^3.0.11" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -889,13 +889,10 @@ | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/@types/node": { | ||||
|             "version": "20.11.28", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", | ||||
|             "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", | ||||
|             "devOptional": true, | ||||
|             "dependencies": { | ||||
|                 "undici-types": "~5.26.4" | ||||
|             } | ||||
|             "version": "20.7.0", | ||||
|             "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz", | ||||
|             "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==", | ||||
|             "devOptional": true | ||||
|         }, | ||||
|         "node_modules/@types/normalize-package-data": { | ||||
|             "version": "2.4.4", | ||||
| @ -955,16 +952,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", | ||||
|             "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", | ||||
|             "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/regexpp": "^4.5.1", | ||||
|                 "@typescript-eslint/scope-manager": "7.4.0", | ||||
|                 "@typescript-eslint/type-utils": "7.4.0", | ||||
|                 "@typescript-eslint/utils": "7.4.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.4.0", | ||||
|                 "@typescript-eslint/scope-manager": "7.2.0", | ||||
|                 "@typescript-eslint/type-utils": "7.2.0", | ||||
|                 "@typescript-eslint/utils": "7.2.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.2.0", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "graphemer": "^1.4.0", | ||||
|                 "ignore": "^5.2.4", | ||||
| @ -973,7 +970,7 @@ | ||||
|                 "ts-api-utils": "^1.0.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -990,19 +987,19 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", | ||||
|             "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", | ||||
|             "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "7.4.0", | ||||
|                 "@typescript-eslint/types": "7.4.0", | ||||
|                 "@typescript-eslint/typescript-estree": "7.4.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.4.0", | ||||
|                 "@typescript-eslint/scope-manager": "7.2.0", | ||||
|                 "@typescript-eslint/types": "7.2.0", | ||||
|                 "@typescript-eslint/typescript-estree": "7.2.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.2.0", | ||||
|                 "debug": "^4.3.4" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1018,16 +1015,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", | ||||
|             "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", | ||||
|             "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "7.4.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.4.0" | ||||
|                 "@typescript-eslint/types": "7.2.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.2.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1035,18 +1032,18 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/type-utils": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", | ||||
|             "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", | ||||
|             "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/typescript-estree": "7.4.0", | ||||
|                 "@typescript-eslint/utils": "7.4.0", | ||||
|                 "@typescript-eslint/typescript-estree": "7.2.0", | ||||
|                 "@typescript-eslint/utils": "7.2.0", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "ts-api-utils": "^1.0.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1062,12 +1059,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", | ||||
|             "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", | ||||
|             "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", | ||||
|             "dev": true, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1075,13 +1072,13 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", | ||||
|             "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", | ||||
|             "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "7.4.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.4.0", | ||||
|                 "@typescript-eslint/types": "7.2.0", | ||||
|                 "@typescript-eslint/visitor-keys": "7.2.0", | ||||
|                 "debug": "^4.3.4", | ||||
|                 "globby": "^11.1.0", | ||||
|                 "is-glob": "^4.0.3", | ||||
| @ -1090,7 +1087,7 @@ | ||||
|                 "ts-api-utils": "^1.0.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1127,21 +1124,21 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/utils": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", | ||||
|             "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", | ||||
|             "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@eslint-community/eslint-utils": "^4.4.0", | ||||
|                 "@types/json-schema": "^7.0.12", | ||||
|                 "@types/semver": "^7.5.0", | ||||
|                 "@typescript-eslint/scope-manager": "7.4.0", | ||||
|                 "@typescript-eslint/types": "7.4.0", | ||||
|                 "@typescript-eslint/typescript-estree": "7.4.0", | ||||
|                 "@typescript-eslint/scope-manager": "7.2.0", | ||||
|                 "@typescript-eslint/types": "7.2.0", | ||||
|                 "@typescript-eslint/typescript-estree": "7.2.0", | ||||
|                 "semver": "^7.5.4" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1152,16 +1149,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "7.4.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", | ||||
|             "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", | ||||
|             "version": "7.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", | ||||
|             "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "7.4.0", | ||||
|                 "@typescript-eslint/types": "7.2.0", | ||||
|                 "eslint-visitor-keys": "^3.4.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^18.18.0 || >=20.0.0" | ||||
|                 "node": "^16.0.0 || >=18.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "type": "opencollective", | ||||
| @ -1189,19 +1186,19 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/cli": { | ||||
|             "version": "8.35.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.35.1.tgz", | ||||
|             "integrity": "sha512-cdFmd6P/eQJdP2lChQ+Fa9b1c2p0bDIPmetVHGCuHiW8ZPkanrvBFtHMUhMu44a1koni9LvN/hu7vIJ/aAC+Rg==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.33.1.tgz", | ||||
|             "integrity": "sha512-Ngt5R6YAmErkSKnWLWt1JilLIKDPIB0P93bzQhb9bQhmg1arFBcl75uiwe6kf6T355vzcNslMaEJyeuqGChmCg==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/node": "^20.1.1", | ||||
|                 "@vitest/snapshot": "^1.2.1", | ||||
|                 "@wdio/config": "8.35.0", | ||||
|                 "@wdio/globals": "8.35.1", | ||||
|                 "@wdio/config": "8.33.1", | ||||
|                 "@wdio/globals": "8.33.1", | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/protocols": "8.32.0", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "@wdio/utils": "8.35.0", | ||||
|                 "@wdio/utils": "8.33.1", | ||||
|                 "async-exit-hook": "^2.0.1", | ||||
|                 "chalk": "^5.2.0", | ||||
|                 "chokidar": "^3.5.3", | ||||
| @ -1216,7 +1213,7 @@ | ||||
|                 "lodash.union": "^4.6.0", | ||||
|                 "read-pkg-up": "10.0.0", | ||||
|                 "recursive-readdir": "^2.2.3", | ||||
|                 "webdriverio": "8.35.1", | ||||
|                 "webdriverio": "8.33.1", | ||||
|                 "yargs": "^17.7.2" | ||||
|             }, | ||||
|             "bin": { | ||||
| @ -1239,14 +1236,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/config": { | ||||
|             "version": "8.35.0", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.35.0.tgz", | ||||
|             "integrity": "sha512-I36sBPMl/+LCyQ3Pwb8gGQM6KxwmUfhOPp16TxN21Qo/Bc0fZfyGIg6KevmRu4DuqpGUm5MMVSfyPhLUkMk3Cg==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.33.1.tgz", | ||||
|             "integrity": "sha512-JB7+tRkEsDJ4QAgJIZ3AaZvlp8pfBH6A5cKcGsaOuLVYMnsRPVkEGQc6n2akN9EPlDA2UjyrPOX6KZHbsSty7w==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "@wdio/utils": "8.35.0", | ||||
|                 "@wdio/utils": "8.33.1", | ||||
|                 "decamelize": "^6.0.0", | ||||
|                 "deepmerge-ts": "^5.0.0", | ||||
|                 "glob": "^10.2.2", | ||||
| @ -1257,28 +1254,28 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/globals": { | ||||
|             "version": "8.35.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.35.1.tgz", | ||||
|             "integrity": "sha512-T3IUFcKXRU9WWleAV72DGFWUiXSSr8SBvpc2cUJrvZ5Je9R2gEsrts5eHCY7amXtfeylfMgy5EayGMajgcna6A==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.33.1.tgz", | ||||
|             "integrity": "sha512-1ud9oq7n9MMNywS/FoMRRWqW6uhcoxgnpXoGeLE2Tr+4f937ABOl+sfZgjycXujyvR7yTL8AROOYajp1Yuv1Xg==", | ||||
|             "dev": true, | ||||
|             "engines": { | ||||
|                 "node": "^16.13 || >=18" | ||||
|             }, | ||||
|             "optionalDependencies": { | ||||
|                 "expect-webdriverio": "^4.11.2", | ||||
|                 "webdriverio": "8.35.1" | ||||
|                 "webdriverio": "8.33.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/local-runner": { | ||||
|             "version": "8.35.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.35.1.tgz", | ||||
|             "integrity": "sha512-PG+bADoY5VoWPmAfRi030rtxbFj68MVPlcwEN0dN1lDdYKz1ATzzGUK12sqCgGz1ktcC7sQzmJZVBklzbvn3mQ==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.33.1.tgz", | ||||
|             "integrity": "sha512-eQp12wHIkyh5zl9fun1qjv5Qvf4mCHPgLs5sKbfo3OK4LadzmD4/QNvDG8DYq/9cyuhVvnHgbLQ3XAnkoPde3w==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/node": "^20.1.0", | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/repl": "8.24.12", | ||||
|                 "@wdio/runner": "8.35.1", | ||||
|                 "@wdio/runner": "8.33.1", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "async-exit-hook": "^2.0.1", | ||||
|                 "split2": "^4.1.0", | ||||
| @ -1316,16 +1313,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/mocha-framework": { | ||||
|             "version": "8.35.0", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.35.0.tgz", | ||||
|             "integrity": "sha512-riO3aMgvGdFFRMpyMk5m480V+mi5EcKk6cjT1TB9L5XEN7Mo/8qthBw9CLgFCZkr4KlR40hgPKSZFHE0rH2GpQ==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.33.1.tgz", | ||||
|             "integrity": "sha512-CxYLE22+tgnMnruElvDGJGR+dE0pxvMZ95agIUYYen69DJ705a74XtTR6zX9COWu6RooBezHgEs3fXev0XL79Q==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/mocha": "^10.0.0", | ||||
|                 "@types/node": "^20.1.0", | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "@wdio/utils": "8.35.0", | ||||
|                 "@wdio/utils": "8.33.1", | ||||
|                 "mocha": "^10.0.0" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -1367,22 +1364,22 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/runner": { | ||||
|             "version": "8.35.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.35.1.tgz", | ||||
|             "integrity": "sha512-5F6cbOYeZjF34Vsnycp5JPnDljI52fmyxsV2O/L3h6F2+83YXpbsqBplw/2G24JtIUudV7VOY/38bUicn1OyXg==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.33.1.tgz", | ||||
|             "integrity": "sha512-i0eRwMCePKkQocWsdkPQpBb1jELyNR5JCwnmOgM3g9fQI6KAf5D4oEUkNDFL/vD4UtgbSRmux7b7j5G01VvuqQ==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/node": "^20.11.28", | ||||
|                 "@wdio/config": "8.35.0", | ||||
|                 "@wdio/globals": "8.35.1", | ||||
|                 "@types/node": "^20.1.0", | ||||
|                 "@wdio/config": "8.33.1", | ||||
|                 "@wdio/globals": "8.33.1", | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "@wdio/utils": "8.35.0", | ||||
|                 "deepmerge-ts": "^5.1.0", | ||||
|                 "expect-webdriverio": "^4.12.0", | ||||
|                 "gaze": "^1.1.3", | ||||
|                 "webdriver": "8.35.0", | ||||
|                 "webdriverio": "8.35.1" | ||||
|                 "@wdio/utils": "8.33.1", | ||||
|                 "deepmerge-ts": "^5.0.0", | ||||
|                 "expect-webdriverio": "^4.11.2", | ||||
|                 "gaze": "^1.1.2", | ||||
|                 "webdriver": "8.33.1", | ||||
|                 "webdriverio": "8.33.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^16.13 || >=18" | ||||
| @ -1429,9 +1426,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@wdio/utils": { | ||||
|             "version": "8.35.0", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.35.0.tgz", | ||||
|             "integrity": "sha512-9KCyn4aS+9tWfthnUkNFVe52AM6QrLGAeIxgGxNlzTAcQGl7jjwdDM7aSK0RjLkWI3a/88DRH21mN/t2LGDmPQ==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.33.1.tgz", | ||||
|             "integrity": "sha512-W0ArrZbs4M23POv8+FPsgHDFxg+wwklfZgLSsjVq2kpCmBCfIPxKSAOgTo/XrcH4We/OnshgBzxLcI+BHDgi4w==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@puppeteer/browsers": "^1.6.0", | ||||
| @ -2084,9 +2081,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/chromedriver": { | ||||
|             "version": "123.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.1.tgz", | ||||
|             "integrity": "sha512-YQUIP/zdlzDIRCZNCv6rEVDSY4RAxo/tDL0OiGPPuai+z8unRNqJr/9V6XTBypVFyDheXNalKt9QxEqdMPuLAQ==", | ||||
|             "version": "122.0.6", | ||||
|             "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.6.tgz", | ||||
|             "integrity": "sha512-Q0r+QlUtiJWMQ5HdYaFa0CtBmLFq3n5JWfmq9mOC00UMBvWxku09gUkvBt457QnYfTM/XHqY/HTFOxHvATnTmA==", | ||||
|             "hasInstallScript": true, | ||||
|             "dependencies": { | ||||
|                 "@testim/chrome-version": "^1.1.4", | ||||
| @ -2691,9 +2688,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/devtools-protocol": { | ||||
|             "version": "0.0.1273771", | ||||
|             "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1273771.tgz", | ||||
|             "integrity": "sha512-QDbb27xcTVReQQW/GHJsdQqGKwYBE7re7gxehj467kKP2DKuYBUj6i2k5LRiAC66J1yZG/9gsxooz/s9pcm0Og==", | ||||
|             "version": "0.0.1263784", | ||||
|             "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1263784.tgz", | ||||
|             "integrity": "sha512-k0SCZMwj587w4F8QYbP5iIbSonL6sd3q8aVJch036r9Tv2t9b5/Oq7AiJ/FJvRuORm/pJNXZtrdNNWlpRnl56A==", | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/diff": { | ||||
| @ -3114,9 +3111,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/eslint-plugin-sonarjs": { | ||||
|             "version": "0.25.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.0.tgz", | ||||
|             "integrity": "sha512-DaZOtpUucEZbvowgKxVFwICV6r0h7jSCAx0IHICvCowP+etFussnhtaiCPSnYAuwVJ+P/6UFUhkv7QJklpXFyA==", | ||||
|             "version": "0.24.0", | ||||
|             "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz", | ||||
|             "integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==", | ||||
|             "dev": true, | ||||
|             "engines": { | ||||
|                 "node": ">=16" | ||||
| @ -3413,9 +3410,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/expect-webdriverio": { | ||||
|             "version": "4.12.1", | ||||
|             "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.12.1.tgz", | ||||
|             "integrity": "sha512-jTfyC2bJbPNw4c8MlEwZNX7SjtPbZ73ysJvr/OGKA9mSKC+toyjU2eMNzHlt9WZO5+wl0RDS1dR7VxHXeu7+zA==", | ||||
|             "version": "4.11.2", | ||||
|             "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.11.2.tgz", | ||||
|             "integrity": "sha512-PK8lrQmRhK8NRtE8i/CJsnKS/QGrHULQW5EfmyKxIiLHnXd7t8dX0dWJn/fiYVtrPKBUyXSL6h52QqvZVc9yGQ==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@vitest/snapshot": "^1.2.2", | ||||
| @ -3699,9 +3696,9 @@ | ||||
|             "dev": true | ||||
|         }, | ||||
|         "node_modules/follow-redirects": { | ||||
|             "version": "1.15.6", | ||||
|             "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", | ||||
|             "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", | ||||
|             "version": "1.15.5", | ||||
|             "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", | ||||
|             "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", | ||||
|             "funding": [ | ||||
|                 { | ||||
|                     "type": "individual", | ||||
| @ -8620,9 +8617,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript": { | ||||
|             "version": "5.4.3", | ||||
|             "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", | ||||
|             "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", | ||||
|             "version": "5.4.2", | ||||
|             "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", | ||||
|             "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", | ||||
|             "dev": true, | ||||
|             "bin": { | ||||
|                 "tsc": "bin/tsc", | ||||
| @ -8657,12 +8654,6 @@ | ||||
|                 "through": "^2.3.8" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/undici-types": { | ||||
|             "version": "5.26.5", | ||||
|             "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", | ||||
|             "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", | ||||
|             "devOptional": true | ||||
|         }, | ||||
|         "node_modules/universalify": { | ||||
|             "version": "0.1.2", | ||||
|             "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", | ||||
| @ -8886,18 +8877,18 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/webdriver": { | ||||
|             "version": "8.35.0", | ||||
|             "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.35.0.tgz", | ||||
|             "integrity": "sha512-D13EroddIXDqdq3jgO8j6sorgTWqTwEiTqwlDoJizpRIgHGBy+UjkNM7XW1yVcvt8gsD2Dei2LQth2tJEnu5Ng==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.33.1.tgz", | ||||
|             "integrity": "sha512-QREF4c08omN9BPh3QDmz5h+OEvjdzDliuEcrDuXoDnHSMxIj1rsonzsgRaM2PXhFZuPeMIiKZYqc7Qg9BGbh6A==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/node": "^20.1.0", | ||||
|                 "@types/ws": "^8.5.3", | ||||
|                 "@wdio/config": "8.35.0", | ||||
|                 "@wdio/config": "8.33.1", | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/protocols": "8.32.0", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "@wdio/utils": "8.35.0", | ||||
|                 "@wdio/utils": "8.33.1", | ||||
|                 "deepmerge-ts": "^5.1.0", | ||||
|                 "got": "^12.6.1", | ||||
|                 "ky": "^0.33.0", | ||||
| @ -8908,23 +8899,23 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/webdriverio": { | ||||
|             "version": "8.35.1", | ||||
|             "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.35.1.tgz", | ||||
|             "integrity": "sha512-YAuKR4JERGiMqCJmm5fEVZ160iiFPyupwALqfXfzrYVcEmKltKPFY/oUCArmi6Uzqd+Sa2Kp9WZtz2Eu1R76JA==", | ||||
|             "version": "8.33.1", | ||||
|             "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.33.1.tgz", | ||||
|             "integrity": "sha512-1DsF8sx1a46AoVYCUpEwJYU74iBAW/U2H5r6p+60ct7dIiFmxmc4uCbOqtf7NLOTgrIzAOaRnT0EsrRICpg5Qw==", | ||||
|             "dev": true, | ||||
|             "dependencies": { | ||||
|                 "@types/node": "^20.1.0", | ||||
|                 "@wdio/config": "8.35.0", | ||||
|                 "@wdio/config": "8.33.1", | ||||
|                 "@wdio/logger": "8.28.0", | ||||
|                 "@wdio/protocols": "8.32.0", | ||||
|                 "@wdio/repl": "8.24.12", | ||||
|                 "@wdio/types": "8.32.4", | ||||
|                 "@wdio/utils": "8.35.0", | ||||
|                 "@wdio/utils": "8.33.1", | ||||
|                 "archiver": "^7.0.0", | ||||
|                 "aria-query": "^5.0.0", | ||||
|                 "css-shorthand-properties": "^1.1.1", | ||||
|                 "css-value": "^0.0.1", | ||||
|                 "devtools-protocol": "^0.0.1273771", | ||||
|                 "devtools-protocol": "^0.0.1263784", | ||||
|                 "grapheme-splitter": "^1.0.2", | ||||
|                 "import-meta-resolve": "^4.0.0", | ||||
|                 "is-plain-obj": "^4.1.0", | ||||
| @ -8936,7 +8927,7 @@ | ||||
|                 "resq": "^1.9.1", | ||||
|                 "rgb2hex": "0.2.5", | ||||
|                 "serialize-error": "^11.0.1", | ||||
|                 "webdriver": "8.35.0" | ||||
|                 "webdriver": "8.33.1" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^16.13 || >=18" | ||||
|  | ||||
| @ -4,19 +4,19 @@ | ||||
|     "type": "module", | ||||
|     "devDependencies": { | ||||
|         "@trivago/prettier-plugin-sort-imports": "^4.3.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^7.4.0", | ||||
|         "@typescript-eslint/parser": "^7.4.0", | ||||
|         "@wdio/cli": "^8.35.1", | ||||
|         "@wdio/local-runner": "^8.35.1", | ||||
|         "@wdio/mocha-framework": "^8.35.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^7.2.0", | ||||
|         "@typescript-eslint/parser": "^7.2.0", | ||||
|         "@wdio/cli": "^8.33.1", | ||||
|         "@wdio/local-runner": "^8.33.1", | ||||
|         "@wdio/mocha-framework": "^8.33.1", | ||||
|         "@wdio/spec-reporter": "^8.32.4", | ||||
|         "eslint": "^8.57.0", | ||||
|         "eslint-config-google": "^0.14.0", | ||||
|         "eslint-plugin-sonarjs": "^0.25.0", | ||||
|         "eslint-plugin-sonarjs": "^0.24.0", | ||||
|         "npm-run-all": "^4.1.5", | ||||
|         "prettier": "^3.2.5", | ||||
|         "ts-node": "^10.9.2", | ||||
|         "typescript": "^5.4.3", | ||||
|         "typescript": "^5.4.2", | ||||
|         "wdio-wait-for": "^3.0.11" | ||||
|     }, | ||||
|     "scripts": { | ||||
| @ -32,6 +32,6 @@ | ||||
|         "node": ">=20" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "chromedriver": "^123.0.1" | ||||
|         "chromedriver": "^122.0.6" | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -55,15 +55,15 @@ for (const [source, rawdest, strip] of otherFiles) { | ||||
|  | ||||
| // This starts the definitions used for esbuild: Our targets, our arguments, the function for running a build, and three | ||||
| // options for building: watching, building, and building the proxy. | ||||
| // Ordered by largest to smallest interface to build even faster | ||||
|  | ||||
| const interfaces = [ | ||||
|     ["admin/AdminInterface/AdminInterface.ts", "admin"], | ||||
|     ["user/UserInterface.ts", "user"], | ||||
|     ["flow/FlowInterface.ts", "flow"], | ||||
|     ["standalone/api-browser/index.ts", "standalone/api-browser"], | ||||
|     ["enterprise/rac/index.ts", "enterprise/rac"], | ||||
|     ["standalone/loading/index.ts", "standalone/loading"], | ||||
|     ["polyfill/poly.ts", "."], | ||||
|     ["standalone/loading/index.ts", "standalone/loading"], | ||||
|     ["flow/FlowInterface.ts", "flow"], | ||||
|     ["user/UserInterface.ts", "user"], | ||||
|     ["enterprise/rac/index.ts", "enterprise/rac"], | ||||
|     ["standalone/api-browser/index.ts", "standalone/api-browser"], | ||||
|     ["admin/AdminInterface/AdminInterface.ts", "admin"], | ||||
| ]; | ||||
|  | ||||
| const baseArgs = { | ||||
| @ -80,32 +80,29 @@ const baseArgs = { | ||||
|     format: "esm", | ||||
| }; | ||||
|  | ||||
| async function buildOneSource(source, dest) { | ||||
|     const DIST = path.join(__dirname, "./dist", dest); | ||||
|     console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`); | ||||
|  | ||||
|     try { | ||||
|         const start = Date.now(); | ||||
|         await esbuild.build({ | ||||
|             ...baseArgs, | ||||
|             entryPoints: [`./src/${source}`], | ||||
|             outdir: DIST, | ||||
|         }); | ||||
|         const end = Date.now(); | ||||
|         console.log( | ||||
|             `[${new Date(end).toISOString()}] Finished build for target ${source} in ${ | ||||
|                 Date.now() - start | ||||
|             }ms`, | ||||
|         ); | ||||
|     } catch (exc) { | ||||
|         console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`); | ||||
| function buildAuthentik(interfaces) { | ||||
|     for (const [source, dest] of interfaces) { | ||||
|         const DIST = path.join(__dirname, "./dist", dest); | ||||
|         console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`); | ||||
|         try { | ||||
|             const start = Date.now(); | ||||
|             esbuild.buildSync({ | ||||
|                 ...baseArgs, | ||||
|                 entryPoints: [`./src/${source}`], | ||||
|                 outdir: DIST, | ||||
|             }); | ||||
|             const end = Date.now(); | ||||
|             console.log( | ||||
|                 `[${new Date(end).toISOString()}] Finished build for target ${source} in ${Date.now() - start}ms`, | ||||
|             ); | ||||
|         } catch (exc) { | ||||
|             console.error( | ||||
|                 `[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function buildAuthentik(interfaces) { | ||||
|     await Promise.allSettled(interfaces.map(([source, dest]) => buildOneSource(source, dest))); | ||||
| } | ||||
|  | ||||
| let timeoutId = null; | ||||
| function debouncedBuild() { | ||||
|     if (timeoutId !== null) { | ||||
| @ -141,11 +138,9 @@ if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === | ||||
|     }); | ||||
| } else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) { | ||||
|     // There's no watch-for-proxy, sorry. | ||||
|     await buildAuthentik( | ||||
|         interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)), | ||||
|     ); | ||||
|     buildAuthentik(interfaces.slice(0, 2)); | ||||
|     process.exit(0); | ||||
| } else { | ||||
|     // And the fallback: just build it. | ||||
|     await buildAuthentik(interfaces); | ||||
|     buildAuthentik(interfaces); | ||||
| } | ||||
|  | ||||
| @ -1,17 +0,0 @@ | ||||
| ### 2024-03-26T09:25:06-0700 | ||||
|  | ||||
| Split the tsconfig file into a base and build variant. | ||||
|  | ||||
| Lesson: This lesson is stored here and not in a comment in tsconfig.json because | ||||
| JSON doesn't like comments. Doug Crockford's purity requirement has doomed an | ||||
| entire generation to keeping its human-facing meta somewhere other than in the | ||||
| file where it belongs. | ||||
|  | ||||
| Lesson: The `extend` command of tsconfig has an unexpected behavior. It is | ||||
| neither a merge or a replace, but some mixture of the two. The buildfile's | ||||
| `compilerOptions` is not a full replacement; instead, each of _its_ top-level | ||||
| fields is a replacement for what is found in the basefile. So while you don't | ||||
| need to include _everything_ in a `compilerOptions` field if you want to change | ||||
| one thing, if you want to modify _one_ path in `compilerOptions.path`, you must | ||||
| include the entire `compilerOptions.path` collection in your buildfile. | ||||
| g | ||||
							
								
								
									
										1299
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1299
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -14,8 +14,8 @@ | ||||
|         "build": "run-s build-locales esbuild:build", | ||||
|         "build-proxy": "run-s build-locales esbuild:build-proxy", | ||||
|         "watch": "run-s build-locales esbuild:watch", | ||||
|         "lint": "cross-env NODE_OPTIONS='--max_old_space_size=8192' eslint . --max-warnings 0 --fix", | ||||
|         "lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=8192' node scripts/eslint-precommit.mjs", | ||||
|         "lint": "eslint . --max-warnings 0 --fix", | ||||
|         "lint:precommit": "node scripts/eslint-precommit.mjs", | ||||
|         "lint:spelling": "node scripts/check-spelling.mjs", | ||||
|         "lit-analyse": "lit-analyzer src", | ||||
|         "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", | ||||
| @ -32,29 +32,29 @@ | ||||
|     "dependencies": { | ||||
|         "@codemirror/lang-html": "^6.4.8", | ||||
|         "@codemirror/lang-javascript": "^6.2.2", | ||||
|         "@codemirror/lang-python": "^6.1.5", | ||||
|         "@codemirror/lang-python": "^6.1.4", | ||||
|         "@codemirror/lang-xml": "^6.1.0", | ||||
|         "@codemirror/legacy-modes": "^6.3.3", | ||||
|         "@codemirror/theme-one-dark": "^6.1.2", | ||||
|         "@formatjs/intl-listformat": "^7.5.5", | ||||
|         "@fortawesome/fontawesome-free": "^6.5.1", | ||||
|         "@goauthentik/api": "^2024.2.2-1711643691", | ||||
|         "@goauthentik/api": "^2024.2.2-1709583949", | ||||
|         "@lit-labs/task": "^3.1.0", | ||||
|         "@lit/reactive-element": "^2.0.4", | ||||
|         "@lit/context": "^1.1.0", | ||||
|         "@lit/localize": "^0.12.1", | ||||
|         "@lit/reactive-element": "^2.0.4", | ||||
|         "@open-wc/lit-helpers": "^0.7.0", | ||||
|         "@patternfly/elements": "^2.4.0", | ||||
|         "@patternfly/patternfly": "^4.224.2", | ||||
|         "@sentry/browser": "^7.109.0", | ||||
|         "@sentry/browser": "^7.106.1", | ||||
|         "@webcomponents/webcomponentsjs": "^2.8.0", | ||||
|         "base64-js": "^1.5.1", | ||||
|         "chart.js": "^4.4.2", | ||||
|         "chartjs-adapter-moment": "^1.0.1", | ||||
|         "codemirror": "^6.0.1", | ||||
|         "construct-style-sheets-polyfill": "^3.1.0", | ||||
|         "core-js": "^3.36.1", | ||||
|         "country-flag-icons": "^1.5.10", | ||||
|         "core-js": "^3.36.0", | ||||
|         "country-flag-icons": "^1.5.9", | ||||
|         "fuse.js": "^7.0.0", | ||||
|         "guacamole-common-js": "^1.5.0", | ||||
|         "lit": "^3.1.2", | ||||
| @ -68,19 +68,18 @@ | ||||
|         "yaml": "^2.4.1" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@babel/core": "^7.24.3", | ||||
|         "@babel/core": "^7.24.0", | ||||
|         "@babel/plugin-proposal-class-properties": "^7.18.6", | ||||
|         "@babel/plugin-proposal-decorators": "^7.24.1", | ||||
|         "@babel/plugin-transform-private-methods": "^7.24.1", | ||||
|         "@babel/plugin-transform-private-property-in-object": "^7.24.1", | ||||
|         "@babel/plugin-transform-runtime": "^7.24.3", | ||||
|         "@babel/preset-env": "^7.24.3", | ||||
|         "@babel/preset-typescript": "^7.24.1", | ||||
|         "@babel/plugin-proposal-decorators": "^7.24.0", | ||||
|         "@babel/plugin-transform-private-methods": "^7.23.3", | ||||
|         "@babel/plugin-transform-private-property-in-object": "^7.23.4", | ||||
|         "@babel/plugin-transform-runtime": "^7.24.0", | ||||
|         "@babel/preset-env": "^7.24.0", | ||||
|         "@babel/preset-typescript": "^7.23.3", | ||||
|         "@hcaptcha/types": "^1.0.3", | ||||
|         "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", | ||||
|         "@lit/localize-tools": "^0.7.2", | ||||
|         "@rollup/plugin-replace": "^5.0.5", | ||||
|         "@spotlightjs/spotlight": "^1.2.16", | ||||
|         "@spotlightjs/spotlight": "^1.2.12", | ||||
|         "@storybook/addon-essentials": "^7.6.17", | ||||
|         "@storybook/addon-links": "^7.6.17", | ||||
|         "@storybook/api": "^7.6.17", | ||||
| @ -91,47 +90,48 @@ | ||||
|         "@trivago/prettier-plugin-sort-imports": "^4.3.0", | ||||
|         "@types/chart.js": "^2.9.41", | ||||
|         "@types/codemirror": "5.60.15", | ||||
|         "@types/grecaptcha": "^3.0.9", | ||||
|         "@types/grecaptcha": "^3.0.8", | ||||
|         "@types/guacamole-common-js": "1.5.2", | ||||
|         "@types/showdown": "^2.0.6", | ||||
|         "@typescript-eslint/eslint-plugin": "^7.4.0", | ||||
|         "@typescript-eslint/parser": "^7.4.0", | ||||
|         "@typescript-eslint/eslint-plugin": "^7.2.0", | ||||
|         "@typescript-eslint/parser": "^7.2.0", | ||||
|         "@rollup/plugin-replace": "^5.0.5", | ||||
|         "rollup-plugin-modify": "^3.0.0", | ||||
|         "rollup-plugin-postcss-lit": "^2.1.0", | ||||
|         "babel-plugin-macros": "^3.1.0", | ||||
|         "babel-plugin-tsconfig-paths": "^1.0.3", | ||||
|         "chokidar": "^3.6.0", | ||||
|         "cross-env": "^7.0.3", | ||||
|         "esbuild": "^0.20.2", | ||||
|         "esbuild": "^0.20.1", | ||||
|         "eslint": "^8.57.0", | ||||
|         "eslint-config-google": "^0.14.0", | ||||
|         "eslint-plugin-custom-elements": "0.0.8", | ||||
|         "eslint-plugin-lit": "^1.11.0", | ||||
|         "eslint-plugin-sonarjs": "^0.25.0", | ||||
|         "eslint-plugin-sonarjs": "^0.24.0", | ||||
|         "eslint-plugin-storybook": "^0.8.0", | ||||
|         "github-slugger": "^2.0.0", | ||||
|         "glob": "^10.3.12", | ||||
|         "glob": "^10.3.10", | ||||
|         "lit-analyzer": "^2.0.3", | ||||
|         "npm-run-all": "^4.1.5", | ||||
|         "prettier": "^3.2.5", | ||||
|         "pseudolocale": "^2.0.0", | ||||
|         "react": "^18.2.0", | ||||
|         "react-dom": "^18.2.0", | ||||
|         "rollup-plugin-modify": "^3.0.0", | ||||
|         "rollup-plugin-postcss-lit": "^2.1.0", | ||||
|         "storybook": "^7.6.17", | ||||
|         "storybook-addon-mock": "^4.3.0", | ||||
|         "ts-lit-plugin": "^2.0.2", | ||||
|         "tslib": "^2.6.2", | ||||
|         "turnstile-types": "^1.2.0", | ||||
|         "typescript": "^5.4.3", | ||||
|         "typescript": "^5.4.2", | ||||
|         "vite-tsconfig-paths": "^4.3.2" | ||||
|     }, | ||||
|     "optionalDependencies": { | ||||
|         "@esbuild/darwin-arm64": "^0.20.1", | ||||
|         "@esbuild/linux-amd64": "^0.18.11", | ||||
|         "@esbuild/linux-arm64": "^0.20.1", | ||||
|         "@rollup/rollup-darwin-arm64": "4.13.2", | ||||
|         "@rollup/rollup-linux-arm64-gnu": "4.13.2", | ||||
|         "@rollup/rollup-linux-x64-gnu": "4.13.2" | ||||
|         "@rollup/rollup-darwin-arm64": "4.13.0", | ||||
|         "@rollup/rollup-linux-arm64-gnu": "4.13.0", | ||||
|         "@rollup/rollup-linux-x64-gnu": "4.13.0" | ||||
|     }, | ||||
|     "engines": { | ||||
|         "node": ">=20" | ||||
|  | ||||
| @ -1,155 +1,266 @@ | ||||
| import "@goauthentik/admin/admin-overview/AdminOverviewPage"; | ||||
| import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; | ||||
| import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; | ||||
| import { RawRoute, makeRoute } from "@goauthentik/elements/router/routeUtils"; | ||||
|  | ||||
| import { html } from "lit"; | ||||
|  | ||||
| export const ROUTES: Route[] = [ | ||||
| export const _ROUTES: RawRoute[] = [ | ||||
|     // Prevent infinite Shell loops | ||||
|     new Route(new RegExp("^/$")).redirect("/administration/overview"), | ||||
|     new Route(new RegExp("^#.*")).redirect("/administration/overview"), | ||||
|     new Route(new RegExp("^/library$")).redirect("/if/user/", true), | ||||
|     ["^/$", "/administration/overview"], | ||||
|     ["^#.*", "/administration/overview"], | ||||
|     ["^/library$", ["/if/user/", true]], | ||||
|     // statically imported since this is the default route | ||||
|     new Route(new RegExp("^/administration/overview$"), async () => { | ||||
|         return html`<ak-admin-overview></ak-admin-overview>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/administration/dashboard/users$"), async () => { | ||||
|         await import("@goauthentik/admin/admin-overview/DashboardUserPage"); | ||||
|         return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/administration/system-tasks$"), async () => { | ||||
|         await import("@goauthentik/admin/system-tasks/SystemTaskListPage"); | ||||
|         return html`<ak-system-task-list></ak-system-task-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/providers$"), async () => { | ||||
|         await import("@goauthentik/admin/providers/ProviderListPage"); | ||||
|         return html`<ak-provider-list></ak-provider-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/providers/ProviderViewPage"); | ||||
|         return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/applications$"), async () => { | ||||
|         await import("@goauthentik/admin/applications/ApplicationListPage"); | ||||
|         return html`<ak-application-list></ak-application-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/applications/ApplicationViewPage"); | ||||
|         return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/sources$"), async () => { | ||||
|         await import("@goauthentik/admin/sources/SourceListPage"); | ||||
|         return html`<ak-source-list></ak-source-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/sources/SourceViewPage"); | ||||
|         return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/property-mappings$"), async () => { | ||||
|         await import("@goauthentik/admin/property-mappings/PropertyMappingListPage"); | ||||
|         return html`<ak-property-mapping-list></ak-property-mapping-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/tokens$"), async () => { | ||||
|         await import("@goauthentik/admin/tokens/TokenListPage"); | ||||
|         return html`<ak-token-list></ak-token-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/core/brands"), async () => { | ||||
|         await import("@goauthentik/admin/brands/BrandListPage"); | ||||
|         return html`<ak-brand-list></ak-brand-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/policy/policies$"), async () => { | ||||
|         await import("@goauthentik/admin/policies/PolicyListPage"); | ||||
|         return html`<ak-policy-list></ak-policy-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/policy/reputation$"), async () => { | ||||
|         await import("@goauthentik/admin/policies/reputation/ReputationListPage"); | ||||
|         return html`<ak-policy-reputation-list></ak-policy-reputation-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/identity/groups$"), async () => { | ||||
|         await import("@goauthentik/admin/groups/GroupListPage"); | ||||
|         return html`<ak-group-list></ak-group-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/groups/GroupViewPage"); | ||||
|         return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/identity/users$"), async () => { | ||||
|         await import("@goauthentik/admin/users/UserListPage"); | ||||
|         return html`<ak-user-list></ak-user-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/users/UserViewPage"); | ||||
|         return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/identity/roles$"), async () => { | ||||
|         await import("@goauthentik/admin/roles/RoleListPage"); | ||||
|         return html`<ak-role-list></ak-role-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/roles/RoleViewPage"); | ||||
|         return html`<ak-role-view roleId=${args.id}></ak-role-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/stages/invitations$"), async () => { | ||||
|         await import("@goauthentik/admin/stages/invitation/InvitationListPage"); | ||||
|         return html`<ak-stage-invitation-list></ak-stage-invitation-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/stages/prompts$"), async () => { | ||||
|         await import("@goauthentik/admin/stages/prompt/PromptListPage"); | ||||
|         return html`<ak-stage-prompt-list></ak-stage-prompt-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/stages$"), async () => { | ||||
|         await import("@goauthentik/admin/stages/StageListPage"); | ||||
|         return html`<ak-stage-list></ak-stage-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/flow/flows$"), async () => { | ||||
|         await import("@goauthentik/admin/flows/FlowListPage"); | ||||
|         return html`<ak-flow-list></ak-flow-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/flows/FlowViewPage"); | ||||
|         return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/events/log$"), async () => { | ||||
|         await import("@goauthentik/admin/events/EventListPage"); | ||||
|         return html`<ak-event-list></ak-event-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => { | ||||
|         await import("@goauthentik/admin/events/EventViewPage"); | ||||
|         return html`<ak-event-view .eventID=${args.id}></ak-event-view>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/events/transports$"), async () => { | ||||
|         await import("@goauthentik/admin/events/TransportListPage"); | ||||
|         return html`<ak-event-transport-list></ak-event-transport-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/events/rules$"), async () => { | ||||
|         await import("@goauthentik/admin/events/RuleListPage"); | ||||
|         return html`<ak-event-rule-list></ak-event-rule-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/outpost/outposts$"), async () => { | ||||
|         await import("@goauthentik/admin/outposts/OutpostListPage"); | ||||
|         return html`<ak-outpost-list></ak-outpost-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/outpost/integrations$"), async () => { | ||||
|         await import("@goauthentik/admin/outposts/ServiceConnectionListPage"); | ||||
|         return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/crypto/certificates$"), async () => { | ||||
|         await import("@goauthentik/admin/crypto/CertificateKeyPairListPage"); | ||||
|         return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/admin/settings$"), async () => { | ||||
|         await import("@goauthentik/admin/admin-settings/AdminSettingsPage"); | ||||
|         return html`<ak-admin-settings></ak-admin-settings>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/blueprints/instances$"), async () => { | ||||
|         await import("@goauthentik/admin/blueprints/BlueprintListPage"); | ||||
|         return html`<ak-blueprint-list></ak-blueprint-list>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/debug$"), async () => { | ||||
|         await import("@goauthentik/admin/DebugPage"); | ||||
|         return html`<ak-admin-debug-page></ak-admin-debug-page>`; | ||||
|     }), | ||||
|     new Route(new RegExp("^/enterprise/licenses$"), async () => { | ||||
|         await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage"); | ||||
|         return html`<ak-enterprise-license-list></ak-enterprise-license-list>`; | ||||
|     }), | ||||
|     [ | ||||
|         "^/administration/overview$", | ||||
|         async () => { | ||||
|             return html`<ak-admin-overview></ak-admin-overview>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/administration/dashboard/users$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/admin-overview/DashboardUserPage"); | ||||
|             return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/administration/system-tasks$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/system-tasks/SystemTaskListPage"); | ||||
|             return html`<ak-system-task-list></ak-system-task-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/core/providers$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/providers/ProviderListPage"); | ||||
|             return html`<ak-provider-list></ak-provider-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/core/providers/(?<id>${ID_REGEX}])$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/providers/ProviderViewPage"); | ||||
|             return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/core/applications$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/applications/ApplicationListPage"); | ||||
|             return html`<ak-application-list></ak-application-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/core/applications/(?<slug>${SLUG_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/applications/ApplicationViewPage"); | ||||
|             return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/core/sources$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/sources/SourceListPage"); | ||||
|             return html`<ak-source-list></ak-source-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/core/sources/(?<slug>${SLUG_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/sources/SourceViewPage"); | ||||
|             return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/core/property-mappings$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/property-mappings/PropertyMappingListPage"); | ||||
|             return html`<ak-property-mapping-list></ak-property-mapping-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/core/tokens$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/tokens/TokenListPage"); | ||||
|             return html`<ak-token-list></ak-token-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/core/brands", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/brands/BrandListPage"); | ||||
|             return html`<ak-brand-list></ak-brand-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/policy/policies$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/policies/PolicyListPage"); | ||||
|             return html`<ak-policy-list></ak-policy-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/policy/reputation$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/policies/reputation/ReputationListPage"); | ||||
|             return html`<ak-policy-reputation-list></ak-policy-reputation-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/identity/groups$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/groups/GroupListPage"); | ||||
|             return html`<ak-group-list></ak-group-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/identity/groups/(?<uuid>${UUID_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/groups/GroupViewPage"); | ||||
|             return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/identity/users$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/users/UserListPage"); | ||||
|             return html`<ak-user-list></ak-user-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/identity/users/(?<id>${ID_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/users/UserViewPage"); | ||||
|             return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/identity/roles$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/roles/RoleListPage"); | ||||
|             return html`<ak-role-list></ak-role-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/identity/roles/(?<id>${UUID_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/roles/RoleViewPage"); | ||||
|             return html`<ak-role-view roleId=${args.id}></ak-role-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/flow/stages/invitations$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/stages/invitation/InvitationListPage"); | ||||
|             return html`<ak-stage-invitation-list></ak-stage-invitation-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/flow/stages/prompts$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/stages/prompt/PromptListPage"); | ||||
|             return html`<ak-stage-prompt-list></ak-stage-prompt-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/flow/stages$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/stages/StageListPage"); | ||||
|             return html`<ak-stage-list></ak-stage-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/flow/flows$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/flows/FlowListPage"); | ||||
|             return html`<ak-flow-list></ak-flow-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/flow/flows/(?<slug>${SLUG_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/flows/FlowViewPage"); | ||||
|             return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/events/log$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/events/EventListPage"); | ||||
|             return html`<ak-event-list></ak-event-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         `^/events/log/(?<id>${UUID_REGEX})$`, | ||||
|         async (args) => { | ||||
|             await import("@goauthentik/admin/events/EventViewPage"); | ||||
|             return html`<ak-event-view .eventID=${args.id}></ak-event-view>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/events/transports$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/events/TransportListPage"); | ||||
|             return html`<ak-event-transport-list></ak-event-transport-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/events/rules$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/events/RuleListPage"); | ||||
|             return html`<ak-event-rule-list></ak-event-rule-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/outpost/outposts$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/outposts/OutpostListPage"); | ||||
|             return html`<ak-outpost-list></ak-outpost-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/outpost/integrations$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/outposts/ServiceConnectionListPage"); | ||||
|             return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/crypto/certificates$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/crypto/CertificateKeyPairListPage"); | ||||
|             return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/admin/settings$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/admin-settings/AdminSettingsPage"); | ||||
|             return html`<ak-admin-settings></ak-admin-settings>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/blueprints/instances$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/blueprints/BlueprintListPage"); | ||||
|             return html`<ak-blueprint-list></ak-blueprint-list>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/debug$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/DebugPage"); | ||||
|             return html`<ak-admin-debug-page></ak-admin-debug-page>`; | ||||
|         }, | ||||
|     ], | ||||
|     [ | ||||
|         "^/enterprise/licenses$", | ||||
|         async () => { | ||||
|             await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage"); | ||||
|             return html`<ak-enterprise-license-list></ak-enterprise-license-list>`; | ||||
|         }, | ||||
|     ], | ||||
| ]; | ||||
|  | ||||
| export const ROUTES = _ROUTES.map(makeRoute); | ||||
|  | ||||
| @ -31,15 +31,9 @@ export class VersionStatusCard extends AdminStatusCard<Version> { | ||||
|                 message: html`${msg(str`${value.versionLatest} is available!`)}`, | ||||
|             }); | ||||
|         } | ||||
|         if (value.versionLatestValid) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success", | ||||
|                 message: html`${msg("Up-to-date!")}`, | ||||
|             }); | ||||
|         } | ||||
|         return Promise.resolve<AdminStatus>({ | ||||
|             icon: "fa fa-question-circle", | ||||
|             message: html`${msg("Latest version unknown")}`, | ||||
|             icon: "fa fa-check-circle pf-m-success", | ||||
|             message: html`${msg("Up-to-date!")}`, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/components/ak-status-label"; | ||||
| import "@goauthentik/elements/events/LogViewer"; | ||||
| import { Form } from "@goauthentik/elements/forms/Form"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import "@goauthentik/elements/forms/SearchSelect"; | ||||
| @ -84,7 +83,28 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> { | ||||
|                 <div class="pf-c-form__group-label"> | ||||
|                     <div class="c-form__horizontal-group"> | ||||
|                         <dl class="pf-c-description-list pf-m-horizontal"> | ||||
|                             <ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer> | ||||
|                             ${(this.result?.logMessages || []).length > 0 | ||||
|                                 ? this.result?.logMessages?.map((m) => { | ||||
|                                       return html`<div class="pf-c-description-list__group"> | ||||
|                                           <dt class="pf-c-description-list__term"> | ||||
|                                               <span class="pf-c-description-list__text" | ||||
|                                                   >${m.log_level}</span | ||||
|                                               > | ||||
|                                           </dt> | ||||
|                                           <dd class="pf-c-description-list__description"> | ||||
|                                               <div class="pf-c-description-list__text"> | ||||
|                                                   ${m.event} | ||||
|                                               </div> | ||||
|                                           </dd> | ||||
|                                       </div>`; | ||||
|                                   }) | ||||
|                                 : html`<div class="pf-c-description-list__group"> | ||||
|                                       <dt class="pf-c-description-list__term"> | ||||
|                                           <span class="pf-c-description-list__text" | ||||
|                                               >${msg("No log messages.")}</span | ||||
|                                           > | ||||
|                                       </dt> | ||||
|                                   </div>`} | ||||
|                         </dl> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
| @ -150,6 +150,10 @@ export class EnterpriseLicenseListPage extends TablePage<License> { | ||||
|  | ||||
|     renderSectionBefore(): TemplateResult { | ||||
|         return html` | ||||
|             <div class="pf-c-banner pf-m-info"> | ||||
|                 ${msg("Enterprise is in preview.")} | ||||
|                 <a href="mailto:hello@goauthentik.io">${msg("Send us feedback!")}</a> | ||||
|             </div> | ||||
|             <section class="pf-c-page__main-section pf-m-no-padding-bottom"> | ||||
|                 <div | ||||
|                     class="pf-l-grid pf-m-gutter pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-3-col-on-lg pf-m-all-3-col-on-xl" | ||||
|  | ||||
| @ -23,15 +23,13 @@ export class BoundStagesList extends Table<FlowStageBinding> { | ||||
|     checkbox = true; | ||||
|     clearOnRefresh = true; | ||||
|  | ||||
|     order = "order"; | ||||
|  | ||||
|     @property() | ||||
|     target?: string; | ||||
|  | ||||
|     async apiEndpoint(page: number): Promise<PaginatedResponse<FlowStageBinding>> { | ||||
|         return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({ | ||||
|             target: this.target || "", | ||||
|             ordering: this.order, | ||||
|             ordering: "order", | ||||
|             page: page, | ||||
|             pageSize: (await uiConfig()).pagination.perPage, | ||||
|         }); | ||||
| @ -39,8 +37,8 @@ export class BoundStagesList extends Table<FlowStageBinding> { | ||||
|  | ||||
|     columns(): TableColumn[] { | ||||
|         return [ | ||||
|             new TableColumn(msg("Order"), "order"), | ||||
|             new TableColumn(msg("Name"), "stage__name"), | ||||
|             new TableColumn(msg("Order")), | ||||
|             new TableColumn(msg("Name")), | ||||
|             new TableColumn(msg("Type")), | ||||
|             new TableColumn(msg("Actions")), | ||||
|         ]; | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { SentryIgnoredError } from "@goauthentik/common/errors"; | ||||
| import "@goauthentik/components/ak-status-label"; | ||||
| import "@goauthentik/elements/events/LogViewer"; | ||||
| import { Form } from "@goauthentik/elements/forms/Form"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
|  | ||||
| @ -56,7 +55,28 @@ export class FlowImportForm extends Form<Flow> { | ||||
|                 <div class="pf-c-form__group-label"> | ||||
|                     <div class="c-form__horizontal-group"> | ||||
|                         <dl class="pf-c-description-list pf-m-horizontal"> | ||||
|                             <ak-log-viewer .logs=${this.result?.logs}></ak-log-viewer> | ||||
|                             ${(this.result?.logs || []).length > 0 | ||||
|                                 ? this.result?.logs?.map((m) => { | ||||
|                                       return html`<div class="pf-c-description-list__group"> | ||||
|                                           <dt class="pf-c-description-list__term"> | ||||
|                                               <span class="pf-c-description-list__text" | ||||
|                                                   >${m.log_level}</span | ||||
|                                               > | ||||
|                                           </dt> | ||||
|                                           <dd class="pf-c-description-list__description"> | ||||
|                                               <div class="pf-c-description-list__text"> | ||||
|                                                   ${m.event} | ||||
|                                               </div> | ||||
|                                           </dd> | ||||
|                                       </div>`; | ||||
|                                   }) | ||||
|                                 : html`<div class="pf-c-description-list__group"> | ||||
|                                       <dt class="pf-c-description-list__term"> | ||||
|                                           <span class="pf-c-description-list__text" | ||||
|                                               >${msg("No log messages.")}</span | ||||
|                                           > | ||||
|                                       </dt> | ||||
|                                   </div>`} | ||||
|                         </dl> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
| @ -31,12 +31,10 @@ export class BoundPoliciesList extends Table<PolicyBinding> { | ||||
|     checkbox = true; | ||||
|     clearOnRefresh = true; | ||||
|  | ||||
|     order = "order"; | ||||
|  | ||||
|     async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> { | ||||
|         return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({ | ||||
|             target: this.target || "", | ||||
|             ordering: this.order, | ||||
|             ordering: "order", | ||||
|             page: page, | ||||
|             pageSize: (await uiConfig()).pagination.perPage, | ||||
|         }); | ||||
|  | ||||
| @ -3,7 +3,6 @@ import { first } from "@goauthentik/common/utils"; | ||||
| import "@goauthentik/components/ak-status-label"; | ||||
| import "@goauthentik/elements/CodeMirror"; | ||||
| import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; | ||||
| import "@goauthentik/elements/events/LogViewer"; | ||||
| import { Form } from "@goauthentik/elements/forms/Form"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import "@goauthentik/elements/forms/SearchSelect"; | ||||
| @ -86,7 +85,28 @@ export class PolicyTestForm extends Form<PolicyTestRequest> { | ||||
|                 <div class="pf-c-form__group-label"> | ||||
|                     <div class="c-form__horizontal-group"> | ||||
|                         <dl class="pf-c-description-list pf-m-horizontal"> | ||||
|                             <ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer> | ||||
|                             ${(this.result?.logMessages || []).length > 0 | ||||
|                                 ? this.result?.logMessages?.map((m) => { | ||||
|                                       return html`<div class="pf-c-description-list__group"> | ||||
|                                           <dt class="pf-c-description-list__term"> | ||||
|                                               <span class="pf-c-description-list__text" | ||||
|                                                   >${m.log_level}</span | ||||
|                                               > | ||||
|                                           </dt> | ||||
|                                           <dd class="pf-c-description-list__description"> | ||||
|                                               <div class="pf-c-description-list__text"> | ||||
|                                                   ${m.event} | ||||
|                                               </div> | ||||
|                                           </dd> | ||||
|                                       </div>`; | ||||
|                                   }) | ||||
|                                 : html`<div class="pf-c-description-list__group"> | ||||
|                                       <dt class="pf-c-description-list__term"> | ||||
|                                           <span class="pf-c-description-list__text" | ||||
|                                               >${msg("No log messages.")}</span | ||||
|                                           > | ||||
|                                       </dt> | ||||
|                                   </div>`} | ||||
|                         </dl> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
| @ -175,7 +175,7 @@ export class OAuth2ProviderViewPage extends AKElement { | ||||
|                   </div>`} | ||||
|             <div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"> | ||||
|                 <div | ||||
|                     class="pf-c-card pf-l-grid__item pf-l-grid__item pf-m-12-col pf-m-4-col-on-xl pf-m-4-col-on-2xl" | ||||
|                     class="pf-c-card pf-l-grid__item pf-l-grid__item pf-m-12-col pf-m-3-col-on-xl pf-m-3-col-on-2xl" | ||||
|                 > | ||||
|                     <div class="pf-c-card__body"> | ||||
|                         <dl class="pf-c-description-list"> | ||||
| @ -255,7 +255,7 @@ export class OAuth2ProviderViewPage extends AKElement { | ||||
|                         </ak-forms-modal> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="pf-c-card pf-l-grid__item pf-m-8-col"> | ||||
|                 <div class="pf-c-card pf-l-grid__item pf-m-7-col"> | ||||
|                     <div class="pf-c-card__body"> | ||||
|                         <form class="pf-c-form"> | ||||
|                             <div class="pf-c-form__group"> | ||||
|  | ||||
| @ -13,7 +13,7 @@ import { customElement, property } from "lit/decorators.js"; | ||||
|  | ||||
| import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; | ||||
|  | ||||
| import { ConnectionToken, RACProvider, RacApi } from "@goauthentik/api"; | ||||
| import { ConnectionToken, Endpoint, RACProvider, RacApi } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-rac-connection-token-list") | ||||
| export class ConnectionTokenListPage extends Table<ConnectionToken> { | ||||
| @ -53,20 +53,20 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> { | ||||
|         return html`<ak-forms-delete-bulk | ||||
|             objectLabel=${msg("Connection Token(s)")} | ||||
|             .objects=${this.selectedElements} | ||||
|             .metadata=${(item: ConnectionToken) => { | ||||
|             .metadata=${(item: Endpoint) => { | ||||
|                 return [ | ||||
|                     { key: msg("Endpoint"), value: item.endpointObj.name }, | ||||
|                     { key: msg("User"), value: item.user.username }, | ||||
|                     { key: msg("Name"), value: item.name }, | ||||
|                     { key: msg("Host"), value: item.host }, | ||||
|                 ]; | ||||
|             }} | ||||
|             .usedBy=${(item: ConnectionToken) => { | ||||
|             .usedBy=${(item: Endpoint) => { | ||||
|                 return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({ | ||||
|                     connectionTokenUuid: item.pk || "", | ||||
|                     connectionTokenUuid: item.pk, | ||||
|                 }); | ||||
|             }} | ||||
|             .delete=${(item: ConnectionToken) => { | ||||
|             .delete=${(item: Endpoint) => { | ||||
|                 return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({ | ||||
|                     connectionTokenUuid: item.pk || "", | ||||
|                     connectionTokenUuid: item.pk, | ||||
|                 }); | ||||
|             }} | ||||
|         > | ||||
|  | ||||
| @ -88,11 +88,7 @@ export class RACProviderViewPage extends AKElement { | ||||
|             <section slot="page-overview" data-tab-title="${msg("Overview")}"> | ||||
|                 ${this.renderTabOverview()} | ||||
|             </section> | ||||
|             <section | ||||
|                 slot="page-connections" | ||||
|                 data-tab-title="${msg("Connections")}" | ||||
|                 class="pf-c-page__main-section pf-m-no-padding-mobile" | ||||
|             > | ||||
|             <section slot="page-connections" data-tab-title="${msg("Connections")}"> | ||||
|                 <div class="pf-c-card"> | ||||
|                     <div class="pf-c-card__body"> | ||||
|                         <ak-rac-connection-token-list | ||||
|  | ||||
| @ -86,28 +86,6 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { | ||||
|                     <span class="pf-c-switch__label">${msg("Enabled")}</span> | ||||
|                 </label> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal name="passwordLoginUpdateInternalPassword"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|                         class="pf-c-switch__input" | ||||
|                         type="checkbox" | ||||
|                         ?checked=${first(this.instance?.passwordLoginUpdateInternalPassword, false)} | ||||
|                     /> | ||||
|                     <span class="pf-c-switch__toggle"> | ||||
|                         <span class="pf-c-switch__toggle-icon"> | ||||
|                             <i class="fas fa-check" aria-hidden="true"></i> | ||||
|                         </span> | ||||
|                     </span> | ||||
|                     <span class="pf-c-switch__label" | ||||
|                         >${msg("Update internal password on login")}</span | ||||
|                     > | ||||
|                 </label> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${msg( | ||||
|                         "When the user logs in to authentik using this source password backend, update their credentials in authentik.", | ||||
|                     )} | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal name="syncUsers"> | ||||
|                 <label class="pf-c-switch"> | ||||
|                     <input | ||||
|  | ||||
| @ -15,7 +15,6 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm"; | ||||
| import "@goauthentik/admin/stages/invitation/InvitationStageForm"; | ||||
| import "@goauthentik/admin/stages/password/PasswordStageForm"; | ||||
| import "@goauthentik/admin/stages/prompt/PromptStageForm"; | ||||
| import "@goauthentik/admin/stages/source/SourceStageForm"; | ||||
| import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; | ||||
| import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; | ||||
| import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm"; | ||||
|  | ||||
| @ -1,4 +1,3 @@ | ||||
| import "@goauthentik/admin/common/ak-license-notice"; | ||||
| import { StageBindingForm } from "@goauthentik/admin/flows/StageBindingForm"; | ||||
| import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm"; | ||||
| import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm"; | ||||
| @ -15,14 +14,12 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm"; | ||||
| import "@goauthentik/admin/stages/invitation/InvitationStageForm"; | ||||
| import "@goauthentik/admin/stages/password/PasswordStageForm"; | ||||
| import "@goauthentik/admin/stages/prompt/PromptStageForm"; | ||||
| import "@goauthentik/admin/stages/source/SourceStageForm"; | ||||
| import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; | ||||
| import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; | ||||
| import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm"; | ||||
| import "@goauthentik/admin/stages/user_write/UserWriteStageForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { AKElement } from "@goauthentik/elements/Base"; | ||||
| import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; | ||||
| import "@goauthentik/elements/forms/ProxyForm"; | ||||
| import "@goauthentik/elements/wizard/FormWizardPage"; | ||||
| import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage"; | ||||
| @ -31,7 +28,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage"; | ||||
|  | ||||
| import { msg, str } from "@lit/localize"; | ||||
| import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; | ||||
| import { CSSResult, TemplateResult, html, nothing } from "lit"; | ||||
| import { CSSResult, TemplateResult, html } from "lit"; | ||||
| import { property } from "lit/decorators.js"; | ||||
|  | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| @ -42,7 +39,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import { FlowStageBinding, Stage, StagesApi, TypeCreate } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-stage-wizard-initial") | ||||
| export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) { | ||||
| export class InitialStageWizardPage extends WizardPage { | ||||
|     @property({ attribute: false }) | ||||
|     stageTypes: TypeCreate[] = []; | ||||
|     sidebarLabel = () => msg("Select type"); | ||||
| @ -65,7 +62,6 @@ export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) { | ||||
|     render(): TemplateResult { | ||||
|         return html`<form class="pf-c-form pf-m-horizontal"> | ||||
|             ${this.stageTypes.map((type) => { | ||||
|                 const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense; | ||||
|                 return html`<div class="pf-c-radio"> | ||||
|                     <input | ||||
|                         class="pf-c-radio__input" | ||||
| @ -86,15 +82,11 @@ export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) { | ||||
|                             ); | ||||
|                             this.host.isValid = true; | ||||
|                         }} | ||||
|                         ?disabled=${requiresEnterprise} | ||||
|                     /> | ||||
|                     <label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`} | ||||
|                         >${type.name}</label | ||||
|                     > | ||||
|                     <span class="pf-c-radio__description">${type.description}${ | ||||
|                         requiresEnterprise ? html`<ak-license-notice></ak-license-notice>` : nothing | ||||
|                     }</span> | ||||
|                     </span> | ||||
|                     <span class="pf-c-radio__description">${type.description}</span> | ||||
|                 </div>`; | ||||
|             })} | ||||
|         </form>`; | ||||
|  | ||||
| @ -1,99 +0,0 @@ | ||||
| import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import "@goauthentik/elements/forms/SearchSelect/index"; | ||||
| import "@goauthentik/elements/utils/TimeDeltaHelp"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { TemplateResult, html } from "lit"; | ||||
| import { customElement } from "lit/decorators.js"; | ||||
| import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { | ||||
|     Source, | ||||
|     SourceStage, | ||||
|     SourcesAllListRequest, | ||||
|     SourcesApi, | ||||
|     StagesApi, | ||||
| } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-stage-source-form") | ||||
| export class SourceStageForm extends BaseStageForm<SourceStage> { | ||||
|     loadInstance(pk: string): Promise<SourceStage> { | ||||
|         return new StagesApi(DEFAULT_CONFIG).stagesSourceRetrieve({ | ||||
|             stageUuid: pk, | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     async send(data: SourceStage): Promise<SourceStage> { | ||||
|         if (this.instance) { | ||||
|             return new StagesApi(DEFAULT_CONFIG).stagesSourceUpdate({ | ||||
|                 stageUuid: this.instance.pk || "", | ||||
|                 sourceStageRequest: data, | ||||
|             }); | ||||
|         } else { | ||||
|             return new StagesApi(DEFAULT_CONFIG).stagesSourceCreate({ | ||||
|                 sourceStageRequest: data, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderForm(): TemplateResult { | ||||
|         return html` | ||||
|             <span> ${msg("TODO.")} </span> | ||||
|             <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.name || "")}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal label=${msg("Source")} ?required=${true} name="source"> | ||||
|                 <ak-search-select | ||||
|                     .fetchObjects=${async (query?: string): Promise<Source[]> => { | ||||
|                         const args: SourcesAllListRequest = { | ||||
|                             ordering: "name", | ||||
|                         }; | ||||
|                         if (query !== undefined) { | ||||
|                             args.search = query; | ||||
|                         } | ||||
|                         const users = await new SourcesApi(DEFAULT_CONFIG).sourcesAllList(args); | ||||
|                         return users.results; | ||||
|                     }} | ||||
|                     .renderElement=${(source: Source): string => { | ||||
|                         return source.name; | ||||
|                     }} | ||||
|                     .renderDescription=${(source: Source): TemplateResult => { | ||||
|                         return html`${source.verboseName}`; | ||||
|                     }} | ||||
|                     .value=${(source: Source | undefined): string | undefined => { | ||||
|                         return source?.pk; | ||||
|                     }} | ||||
|                     .selected=${(source: Source): boolean => { | ||||
|                         return source.pk === this.instance?.source; | ||||
|                     }} | ||||
|                 > | ||||
|                 </ak-search-select> | ||||
|             </ak-form-element-horizontal> | ||||
|             <ak-form-element-horizontal | ||||
|                 label=${msg("Resume timeout")} | ||||
|                 ?required=${true} | ||||
|                 name="resumeTimeout" | ||||
|             > | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     value="${ifDefined(this.instance?.resumeTimeout || "minutes=10")}" | ||||
|                     class="pf-c-form-control" | ||||
|                     required | ||||
|                 /> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     ${msg( | ||||
|                         "Amount of time a user can take to return from the source to continue the flow.", | ||||
|                     )} | ||||
|                 </p> | ||||
|                 <ak-utils-time-delta-help></ak-utils-time-delta-help> | ||||
|             </ak-form-element-horizontal> | ||||
|         `; | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	