diff --git a/.github/workflows/ci-outpost.yml b/.github/workflows/ci-outpost.yml index fe8ebf3b8..517407003 100644 --- a/.github/workflows/ci-outpost.yml +++ b/.github/workflows/ci-outpost.yml @@ -29,7 +29,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.52.2 + version: v1.54.2 args: --timeout 5000s --verbose skip-cache: true test-unittest: diff --git a/.gitignore b/.gitignore index 17f1a196d..713258ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,6 @@ data/ .netlify .ruff_cache source_docs/ + +### Golang ### +/vendor/ diff --git a/Dockerfile b/Dockerfile index 95d20d999..6556ed3a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api RUN npm run build # Stage 3: Build go proxy -FROM docker.io/golang:1.21.1-bookworm AS go-builder +FROM docker.io/golang:1.21.2-bookworm AS go-builder WORKDIR /go/src/goauthentik.io diff --git a/Makefile b/Makefile index 8a122b7e1..bb7f70a43 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ dev-drop-db: dropdb -U ${pg_user} -h ${pg_host} ${pg_name} # Also remove the test-db if it exists dropdb -U ${pg_user} -h ${pg_host} test_${pg_name} || true - echo redis-cli -n 0 flushall + redis-cli -n 0 flushall dev-create-db: createdb -U ${pg_user} -h ${pg_host} ${pg_name} diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 09a5dc553..be59dc1c1 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -190,6 +190,7 @@ class UserSerializer(ModelSerializer): "uid", "path", "type", + "uuid", ] extra_kwargs = { "name": {"allow_blank": True}, diff --git a/authentik/events/monitored_tasks.py b/authentik/events/monitored_tasks.py index 9acee40b6..70f59f610 100644 --- a/authentik/events/monitored_tasks.py +++ b/authentik/events/monitored_tasks.py @@ -206,8 +206,8 @@ def prefill_task(func): task_call_module=func.__module__, task_call_func=func.__name__, # We don't have real values for these attributes but they cannot be null - start_timestamp=default_timer(), - finish_timestamp=default_timer(), + start_timestamp=0, + finish_timestamp=0, finish_time=datetime.now(), ).save(86400) LOGGER.debug("prefilled task", task_name=func.__name__) diff --git a/authentik/flows/apps.py b/authentik/flows/apps.py index e01640bfc..45ebb8489 100644 --- a/authentik/flows/apps.py +++ b/authentik/flows/apps.py @@ -8,6 +8,11 @@ GAUGE_FLOWS_CACHED = Gauge( "authentik_flows_cached", "Cached flows", ) +HIST_FLOW_EXECUTION_STAGE_TIME = Histogram( + "authentik_flows_execution_stage_time", + "Duration each stage took to execute.", + ["stage_type", "method"], +) HIST_FLOWS_PLAN_TIME = Histogram( "authentik_flows_plan_time", "Duration to build a plan for a flow", diff --git a/authentik/flows/views/executor.py b/authentik/flows/views/executor.py index 92b944670..773650c00 100644 --- a/authentik/flows/views/executor.py +++ b/authentik/flows/views/executor.py @@ -24,6 +24,7 @@ from structlog.stdlib import BoundLogger, get_logger from authentik.core.models import Application from authentik.events.models import Event, EventAction, cleanse_dict +from authentik.flows.apps import HIST_FLOW_EXECUTION_STAGE_TIME from authentik.flows.challenge import ( Challenge, ChallengeResponse, @@ -266,17 +267,21 @@ class FlowExecutorView(APIView): ) def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Get the next pending challenge from the currently active flow.""" + class_path = class_to_path(self.current_stage_view.__class__) self._logger.debug( "f(exec): Passing GET", - view_class=class_to_path(self.current_stage_view.__class__), + view_class=class_path, stage=self.current_stage, ) try: with Hub.current.start_span( op="authentik.flow.executor.stage", - description=class_to_path(self.current_stage_view.__class__), - ) as span: - span.set_data("Method", "GET") + description=class_path, + ) as span, HIST_FLOW_EXECUTION_STAGE_TIME.labels( + method=request.method.upper(), + stage_type=class_path, + ).time(): + span.set_data("Method", request.method.upper()) span.set_data("authentik Stage", self.current_stage_view) span.set_data("authentik Flow", self.flow.slug) stage_response = self.current_stage_view.dispatch(request) @@ -310,17 +315,21 @@ class FlowExecutorView(APIView): ) def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Solve the previously retrieved challenge and advanced to the next stage.""" + class_path = class_to_path(self.current_stage_view.__class__) self._logger.debug( "f(exec): Passing POST", - view_class=class_to_path(self.current_stage_view.__class__), + view_class=class_path, stage=self.current_stage, ) try: with Hub.current.start_span( op="authentik.flow.executor.stage", - description=class_to_path(self.current_stage_view.__class__), - ) as span: - span.set_data("Method", "POST") + description=class_path, + ) as span, HIST_FLOW_EXECUTION_STAGE_TIME.labels( + method=request.method.upper(), + stage_type=class_path, + ).time(): + span.set_data("Method", request.method.upper()) span.set_data("authentik Stage", self.current_stage_view) span.set_data("authentik Flow", self.flow.slug) stage_response = self.current_stage_view.dispatch(request) diff --git a/authentik/lib/config.py b/authentik/lib/config.py index 043f77460..63aa3493a 100644 --- a/authentik/lib/config.py +++ b/authentik/lib/config.py @@ -24,7 +24,7 @@ ENVIRONMENT = os.getenv(f"{ENV_PREFIX}_ENV", "local") def get_path_from_dict(root: dict, path: str, sep=".", default=None) -> Any: - """Recursively walk through `root`, checking each part of `path` split by `sep`. + """Recursively walk through `root`, checking each part of `path` separated by `sep`. If at any point a dict does not exist, return default""" for comp in path.split(sep): if root and comp in root: @@ -34,7 +34,19 @@ def get_path_from_dict(root: dict, path: str, sep=".", default=None) -> Any: return root -@dataclass +def set_path_in_dict(root: dict, path: str, value: Any, sep="."): + """Recursively walk through `root`, checking each part of `path` separated by `sep` + and setting the last value to `value`""" + # Walk each component of the path + path_parts = path.split(sep) + for comp in path_parts[:-1]: + if comp not in root: + root[comp] = {} + root = root.get(comp, {}) + root[path_parts[-1]] = value + + +@dataclass(slots=True) class Attr: """Single configuration attribute""" @@ -55,6 +67,10 @@ class Attr: # to the config file containing this change or the file containing this value source: Optional[str] = field(default=None) + def __post_init__(self): + if isinstance(self.value, Attr): + raise RuntimeError(f"config Attr with nested Attr for source {self.source}") + class AttrEncoder(JSONEncoder): """JSON encoder that can deal with `Attr` classes""" @@ -227,15 +243,7 @@ class ConfigLoader: def set(self, path: str, value: Any, sep="."): """Set value using same syntax as get()""" - # Walk sub_dicts before parsing path - root = self.raw - # Walk each component of the path - path_parts = path.split(sep) - for comp in path_parts[:-1]: - if comp not in root: - root[comp] = {} - root = root.get(comp, {}) - root[path_parts[-1]] = Attr(value) + set_path_in_dict(self.raw, path, Attr(value), sep=sep) CONFIG = ConfigLoader() diff --git a/authentik/outposts/channels.py b/authentik/outposts/channels.py index 8b3f978ac..f0b656a47 100644 --- a/authentik/outposts/channels.py +++ b/authentik/outposts/channels.py @@ -27,6 +27,9 @@ class WebsocketMessageInstruction(IntEnum): # Message sent by us to trigger an Update TRIGGER_UPDATE = 2 + # Provider specific message + PROVIDER_SPECIFIC = 3 + @dataclass(slots=True) class WebsocketMessage: @@ -131,3 +134,14 @@ class OutpostConsumer(AuthJsonConsumer): self.send_json( asdict(WebsocketMessage(instruction=WebsocketMessageInstruction.TRIGGER_UPDATE)) ) + + def event_provider_specific(self, event): + """Event handler which can be called by provider-specific + implementations to send specific messages to the outpost""" + self.send_json( + asdict( + WebsocketMessage( + instruction=WebsocketMessageInstruction.PROVIDER_SPECIFIC, args=event + ) + ) + ) diff --git a/authentik/outposts/tasks.py b/authentik/outposts/tasks.py index 227127352..ddb0d5352 100644 --- a/authentik/outposts/tasks.py +++ b/authentik/outposts/tasks.py @@ -5,7 +5,6 @@ from socket import gethostname from typing import Any, Optional from urllib.parse import urlparse -import yaml from asgiref.sync import async_to_sync from channels.layers import get_channel_layer from django.core.cache import cache @@ -16,6 +15,7 @@ from docker.constants import DEFAULT_UNIX_SOCKET from kubernetes.config.incluster_config import SERVICE_TOKEN_FILENAME from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION from structlog.stdlib import get_logger +from yaml import safe_load from authentik.events.monitored_tasks import ( MonitoredTask, @@ -279,7 +279,7 @@ def outpost_connection_discovery(self: MonitoredTask): with kubeconfig_path.open("r", encoding="utf8") as _kubeconfig: KubernetesServiceConnection.objects.create( name=kubeconfig_local_name, - kubeconfig=yaml.safe_load(_kubeconfig), + kubeconfig=safe_load(_kubeconfig), ) unix_socket_path = urlparse(DEFAULT_UNIX_SOCKET).path socket = Path(unix_socket_path) diff --git a/authentik/policies/apps.py b/authentik/policies/apps.py index 17ef4a3a4..d66b77487 100644 --- a/authentik/policies/apps.py +++ b/authentik/policies/apps.py @@ -7,7 +7,11 @@ GAUGE_POLICIES_CACHED = Gauge( "authentik_policies_cached", "Cached Policies", ) - +HIST_POLICIES_ENGINE_TOTAL_TIME = Histogram( + "authentik_policies_engine_time_total_seconds", + "(Total) Duration the policy engine took to evaluate a result.", + ["obj_type", "obj_pk"], +) HIST_POLICIES_EXECUTION_TIME = Histogram( "authentik_policies_execution_time", "Execution times for single policies", @@ -17,6 +21,7 @@ HIST_POLICIES_EXECUTION_TIME = Histogram( "binding_target_name", "object_pk", "object_type", + "mode", ], ) diff --git a/authentik/policies/engine.py b/authentik/policies/engine.py index a3d8a3191..58295f321 100644 --- a/authentik/policies/engine.py +++ b/authentik/policies/engine.py @@ -1,6 +1,7 @@ """authentik policy engine""" from multiprocessing import Pipe, current_process from multiprocessing.connection import Connection +from timeit import default_timer from typing import Iterator, Optional from django.core.cache import cache @@ -10,6 +11,8 @@ from sentry_sdk.tracing import Span from structlog.stdlib import BoundLogger, get_logger from authentik.core.models import User +from authentik.lib.utils.reflection import class_to_path +from authentik.policies.apps import HIST_POLICIES_ENGINE_TOTAL_TIME, HIST_POLICIES_EXECUTION_TIME from authentik.policies.exceptions import PolicyEngineException from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel, PolicyEngineMode from authentik.policies.process import PolicyProcess, cache_key @@ -77,6 +80,33 @@ class PolicyEngine: if binding.policy is not None and binding.policy.__class__ == Policy: raise PolicyEngineException(f"Policy '{binding.policy}' is root type") + def _check_cache(self, binding: PolicyBinding): + if not self.use_cache: + return False + before = default_timer() + key = cache_key(binding, self.request) + cached_policy = cache.get(key, None) + duration = max(default_timer() - before, 0) + if not cached_policy: + return False + self.logger.debug( + "P_ENG: Taking result from cache", + binding=binding, + cache_key=key, + request=self.request, + ) + HIST_POLICIES_EXECUTION_TIME.labels( + binding_order=binding.order, + binding_target_type=binding.target_type, + binding_target_name=binding.target_name, + object_pk=str(self.request.obj.pk), + object_type=class_to_path(self.request.obj.__class__), + mode="cache_retrieve", + ).observe(duration) + # It's a bit silly to time this, but + self.__cached_policies.append(cached_policy) + return True + def build(self) -> "PolicyEngine": """Build wrapper which monitors performance""" with ( @@ -84,6 +114,10 @@ class PolicyEngine: op="authentik.policy.engine.build", description=self.__pbm, ) as span, + HIST_POLICIES_ENGINE_TOTAL_TIME.labels( + obj_type=class_to_path(self.__pbm.__class__), + obj_pk=str(self.__pbm.pk), + ).time(), ): span: Span span.set_data("pbm", self.__pbm) @@ -92,16 +126,7 @@ class PolicyEngine: self.__expected_result_count += 1 self._check_policy_type(binding) - key = cache_key(binding, self.request) - cached_policy = cache.get(key, None) - if cached_policy and self.use_cache: - self.logger.debug( - "P_ENG: Taking result from cache", - binding=binding, - cache_key=key, - request=self.request, - ) - self.__cached_policies.append(cached_policy) + if self._check_cache(binding): continue self.logger.debug("P_ENG: Evaluating policy", binding=binding, request=self.request) our_end, task_end = Pipe(False) diff --git a/authentik/policies/process.py b/authentik/policies/process.py index 89dc548da..aa3ed9ff5 100644 --- a/authentik/policies/process.py +++ b/authentik/policies/process.py @@ -11,6 +11,7 @@ from structlog.stdlib import get_logger from authentik.events.models import Event, EventAction from authentik.lib.config import CONFIG from authentik.lib.utils.errors import exception_to_string +from authentik.lib.utils.reflection import class_to_path from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME from authentik.policies.exceptions import PolicyException from authentik.policies.models import PolicyBinding @@ -128,9 +129,8 @@ class PolicyProcess(PROCESS_CLASS): binding_target_type=self.binding.target_type, binding_target_name=self.binding.target_name, object_pk=str(self.request.obj.pk), - object_type=( - f"{self.request.obj._meta.app_label}.{self.request.obj._meta.model_name}" - ), + object_type=class_to_path(self.request.obj.__class__), + mode="execute_process", ).time(), ): span: Span diff --git a/authentik/policies/signals.py b/authentik/policies/signals.py index 52c5ec767..d7949330f 100644 --- a/authentik/policies/signals.py +++ b/authentik/policies/signals.py @@ -17,7 +17,7 @@ LOGGER = get_logger() @receiver(monitoring_set) def monitoring_set_policies(sender, **kwargs): """set policy gauges""" - GAUGE_POLICIES_CACHED.set(len(cache.keys(f"{CACHE_PREFIX}_*") or [])) + GAUGE_POLICIES_CACHED.set(len(cache.keys(f"{CACHE_PREFIX}*") or [])) @receiver(post_save, sender=Policy) diff --git a/authentik/providers/proxy/apps.py b/authentik/providers/proxy/apps.py index 5e49fe181..4e1a9a883 100644 --- a/authentik/providers/proxy/apps.py +++ b/authentik/providers/proxy/apps.py @@ -9,3 +9,7 @@ class AuthentikProviderProxyConfig(ManagedAppConfig): label = "authentik_providers_proxy" verbose_name = "authentik Providers.Proxy" default = True + + def reconcile_load_providers_proxy_signals(self): + """Load proxy signals""" + self.import_module("authentik.providers.proxy.signals") diff --git a/authentik/providers/proxy/signals.py b/authentik/providers/proxy/signals.py new file mode 100644 index 000000000..3e199d3c3 --- /dev/null +++ b/authentik/providers/proxy/signals.py @@ -0,0 +1,20 @@ +"""Proxy provider signals""" +from django.contrib.auth.signals import user_logged_out +from django.db.models.signals import pre_delete +from django.dispatch import receiver +from django.http import HttpRequest + +from authentik.core.models import AuthenticatedSession, User +from authentik.providers.proxy.tasks import proxy_on_logout + + +@receiver(user_logged_out) +def logout_proxy_revoke_direct(sender: type[User], request: HttpRequest, **_): + """Catch logout by direct logout and forward to proxy providers""" + proxy_on_logout.delay(request.session.session_key) + + +@receiver(pre_delete, sender=AuthenticatedSession) +def logout_proxy_revoke(sender: type[AuthenticatedSession], instance: AuthenticatedSession, **_): + """Catch logout by expiring sessions being deleted""" + proxy_on_logout.delay(instance.session_key) diff --git a/authentik/providers/proxy/tasks.py b/authentik/providers/proxy/tasks.py index a5a4dc45f..630b0d186 100644 --- a/authentik/providers/proxy/tasks.py +++ b/authentik/providers/proxy/tasks.py @@ -1,6 +1,9 @@ """proxy provider tasks""" +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer from django.db import DatabaseError, InternalError, ProgrammingError +from authentik.outposts.models import Outpost, OutpostState, OutpostType from authentik.providers.proxy.models import ProxyProvider from authentik.root.celery import CELERY_APP @@ -13,3 +16,20 @@ def proxy_set_defaults(): for provider in ProxyProvider.objects.all(): provider.set_oauth_defaults() provider.save() + + +@CELERY_APP.task() +def proxy_on_logout(session_id: str): + """Update outpost instances connected to a single outpost""" + layer = get_channel_layer() + for outpost in Outpost.objects.filter(type=OutpostType.PROXY): + for state in OutpostState.for_outpost(outpost): + for channel in state.channel_ids: + async_to_sync(layer.send)( + channel, + { + "type": "event.provider.specific", + "sub_type": "logout", + "session_id": session_id, + }, + ) diff --git a/authentik/providers/saml/api/providers.py b/authentik/providers/saml/api/providers.py index 891289a08..226ec7e58 100644 --- a/authentik/providers/saml/api/providers.py +++ b/authentik/providers/saml/api/providers.py @@ -146,6 +146,7 @@ class SAMLProviderSerializer(ProviderSerializer): "signing_kp", "verification_kp", "sp_binding", + "default_relay_state", "url_download_metadata", "url_sso_post", "url_sso_redirect", diff --git a/authentik/providers/saml/migrations/0013_samlprovider_default_relay_state.py b/authentik/providers/saml/migrations/0013_samlprovider_default_relay_state.py new file mode 100644 index 000000000..5ece7f52d --- /dev/null +++ b/authentik/providers/saml/migrations/0013_samlprovider_default_relay_state.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.6 on 2023-10-08 20:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("authentik_providers_saml", "0012_managed"), + ] + + operations = [ + migrations.AddField( + model_name="samlprovider", + name="default_relay_state", + field=models.TextField( + blank=True, + default="", + help_text="Default relay_state value for IDP-initiated logins", + ), + ), + ] diff --git a/authentik/providers/saml/models.py b/authentik/providers/saml/models.py index e92f62943..1a706df8e 100644 --- a/authentik/providers/saml/models.py +++ b/authentik/providers/saml/models.py @@ -138,6 +138,10 @@ class SAMLProvider(Provider): verbose_name=_("Signing Keypair"), ) + default_relay_state = models.TextField( + default="", blank=True, help_text=_("Default relay_state value for IDP-initiated logins") + ) + @property def launch_url(self) -> Optional[str]: """Use IDP-Initiated SAML flow as launch URL""" diff --git a/authentik/providers/saml/processors/authn_request_parser.py b/authentik/providers/saml/processors/authn_request_parser.py index 30eddf0d3..5587cb695 100644 --- a/authentik/providers/saml/processors/authn_request_parser.py +++ b/authentik/providers/saml/processors/authn_request_parser.py @@ -175,4 +175,7 @@ class AuthNRequestParser: def idp_initiated(self) -> AuthNRequest: """Create IdP Initiated AuthNRequest""" - return AuthNRequest() + relay_state = None + if self.provider.default_relay_state != "": + relay_state = self.provider.default_relay_state + return AuthNRequest(relay_state=relay_state) diff --git a/authentik/providers/saml/tests/test_auth_n_request.py b/authentik/providers/saml/tests/test_auth_n_request.py index 69326bd5f..df19eb736 100644 --- a/authentik/providers/saml/tests/test_auth_n_request.py +++ b/authentik/providers/saml/tests/test_auth_n_request.py @@ -8,6 +8,7 @@ from authentik.blueprints.tests import apply_blueprint from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow from authentik.crypto.models import CertificateKeyPair from authentik.events.models import Event, EventAction +from authentik.lib.generators import generate_id from authentik.lib.tests.utils import get_request from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.assertion import AssertionProcessor @@ -264,3 +265,10 @@ class TestAuthNRequest(TestCase): events.first().context["message"], "Failed to evaluate property-mapping: 'test'", ) + + def test_idp_initiated(self): + """Test IDP-initiated login""" + self.provider.default_relay_state = generate_id() + request = AuthNRequestParser(self.provider).idp_initiated() + self.assertEqual(request.id, None) + self.assertEqual(request.relay_state, self.provider.default_relay_state) diff --git a/authentik/root/test_runner.py b/authentik/root/test_runner.py index 5141613b0..02425f016 100644 --- a/authentik/root/test_runner.py +++ b/authentik/root/test_runner.py @@ -1,8 +1,10 @@ """Integrate ./manage.py test with pytest""" +import os from argparse import ArgumentParser from unittest import TestCase from django.conf import settings +from django.test.runner import DiscoverRunner from authentik.lib.config import CONFIG from authentik.lib.sentry import sentry_init @@ -12,13 +14,11 @@ from tests.e2e.utils import get_docker_tag TestCase.maxDiff = None -class PytestTestRunner: # pragma: no cover +class PytestTestRunner(DiscoverRunner): # pragma: no cover """Runs pytest to discover and run tests.""" def __init__(self, verbosity=1, failfast=False, keepdb=False, **kwargs): - self.verbosity = verbosity - self.failfast = failfast - self.keepdb = keepdb + super().__init__(verbosity, failfast, keepdb, **kwargs) self.args = [] if self.failfast: @@ -47,16 +47,61 @@ class PytestTestRunner: # pragma: no cover @classmethod def add_arguments(cls, parser: ArgumentParser): """Add more pytest-specific arguments""" - parser.add_argument("--randomly-seed", type=int) - parser.add_argument("--keepdb", action="store_true") + parser.add_argument( + "--randomly-seed", + type=int, + help="Set the seed that pytest-randomly uses (int), or pass the special value 'last'" + "to reuse the seed from the previous run." + "Default behaviour: use random.Random().getrandbits(32), so the seed is" + "different on each run.", + ) + parser.add_argument( + "--keepdb", action="store_true", help="Preserves the test DB between runs." + ) - def run_tests(self, test_labels): + def run_tests(self, test_labels, extra_tests=None, **kwargs): """Run pytest and return the exitcode. It translates some of Django's test command option to pytest's. + It is supported to only run specific classes and methods using + a dotted module name i.e. foo.bar[.Class[.method]] + + The extra_tests argument has been deprecated since Django 5.x + It is kept for compatibility with PyCharm's Django test runner. """ + for label in test_labels: + valid_label_found = False + label_as_path = os.path.abspath(label) + # File path has been specified + if os.path.exists(label_as_path): + self.args.append(label_as_path) + valid_label_found = True + else: + # Already correctly formatted test found (file_path::class::method) + if "::" in label: + self.args.append(label) + valid_label_found = True + # Convert dotted module path to file_path::class::method + else: + path_pieces = label.split(".") + # Check whether only class or class and method are specified + for i in range(-1, -3, -1): + path = os.path.join(*path_pieces[:i]) + ".py" + label_as_path = os.path.abspath(path) + if os.path.exists(label_as_path): + path_method = label_as_path + "::" + "::".join(path_pieces[i:]) + self.args.append(path_method) + valid_label_found = True + break + + if not valid_label_found: + raise RuntimeError( + f"One of the test labels: {label!r}, " + f"is not supported. Use a dotted module name or " + f"path instead." + ) + import pytest - self.args.extend(test_labels) return pytest.main(self.args) diff --git a/authentik/sources/ldap/sync/base.py b/authentik/sources/ldap/sync/base.py index a131d935c..7490449ec 100644 --- a/authentik/sources/ldap/sync/base.py +++ b/authentik/sources/ldap/sync/base.py @@ -9,7 +9,7 @@ from structlog.stdlib import BoundLogger, get_logger from authentik.core.exceptions import PropertyMappingExpressionException from authentik.events.models import Event, EventAction -from authentik.lib.config import CONFIG +from authentik.lib.config import CONFIG, set_path_in_dict from authentik.lib.merge import MERGE_LIST_UNIQUE from authentik.sources.ldap.auth import LDAP_DISTINGUISHED_NAME from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource @@ -164,7 +164,7 @@ class BaseLDAPSynchronizer: if object_field.startswith("attributes."): # Because returning a list might desired, we can't # rely on self._flatten here. Instead, just save the result as-is - properties["attributes"][object_field.replace("attributes.", "")] = value + set_path_in_dict(properties, object_field, value) else: properties[object_field] = self._flatten(value) except PropertyMappingExpressionException as exc: diff --git a/authentik/stages/user_write/stage.py b/authentik/stages/user_write/stage.py index 98494fae1..5a4c80974 100644 --- a/authentik/stages/user_write/stage.py +++ b/authentik/stages/user_write/stage.py @@ -14,6 +14,7 @@ from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.stage import StageView from authentik.flows.views.executor import FlowExecutorView +from authentik.lib.config import set_path_in_dict from authentik.stages.password import BACKEND_INBUILT from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT @@ -44,12 +45,7 @@ class UserWriteStageView(StageView): # this is just a sanity check to ensure that is removed if parts[0] == "attributes": parts = parts[1:] - attrs = user.attributes - for comp in parts[:-1]: - if comp not in attrs: - attrs[comp] = {} - attrs = attrs.get(comp) - attrs[parts[-1]] = value + set_path_in_dict(user.attributes, ".".join(parts), value) def ensure_user(self) -> tuple[Optional[User], bool]: """Ensure a user exists""" diff --git a/blueprints/schema.json b/blueprints/schema.json index 7f7757a28..9dc77b796 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -4826,6 +4826,11 @@ ], "title": "Service Provider Binding", "description": "This determines how authentik sends the response back to the Service Provider." + }, + "default_relay_state": { + "type": "string", + "title": "Default relay state", + "description": "Default relay_state value for IDP-initiated logins" } }, "required": [] @@ -7427,146 +7432,32 @@ "model_authentik_stages_invitation.invitation": { "type": "object", "properties": { + "name": { + "type": "string", + "maxLength": 50, + "minLength": 1, + "pattern": "^[-a-zA-Z0-9_]+$", + "title": "Name" + }, "expires": { "type": "string", "format": "date-time", "title": "Expires" }, - "user": { + "fixed_data": { "type": "object", - "properties": { - "username": { - "type": "string", - "maxLength": 150, - "minLength": 1, - "title": "Username" - }, - "name": { - "type": "string", - "title": "Name", - "description": "User's display name." - }, - "is_active": { - "type": "boolean", - "title": "Active", - "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts." - }, - "last_login": { - "type": [ - "string", - "null" - ], - "format": "date-time", - "title": "Last login" - }, - "groups": { - "type": "array", - "items": { - "type": "integer" - }, - "title": "Groups" - }, - "email": { - "type": "string", - "format": "email", - "maxLength": 254, - "title": "Email address" - }, - "attributes": { - "type": "object", - "additionalProperties": true, - "title": "Attributes" - }, - "path": { - "type": "string", - "minLength": 1, - "title": "Path" - }, - "type": { - "type": "string", - "enum": [ - "internal", - "external", - "service_account", - "internal_service_account" - ], - "title": "Type" - } - }, - "required": [ - "username", - "name" - ], - "title": "User" + "additionalProperties": true, + "title": "Fixed data" }, - "application": { - "type": "object", - "properties": { - "name": { - "type": "string", - "minLength": 1, - "title": "Name", - "description": "Application's display Name." - }, - "slug": { - "type": "string", - "maxLength": 50, - "minLength": 1, - "pattern": "^[-a-zA-Z0-9_]+$", - "title": "Slug", - "description": "Internal application name, used in URLs." - }, - "provider": { - "type": "integer", - "title": "Provider" - }, - "backchannel_providers": { - "type": "array", - "items": { - "type": "integer" - }, - "title": "Backchannel providers" - }, - "open_in_new_tab": { - "type": "boolean", - "title": "Open in new tab", - "description": "Open launch URL in a new browser tab or window." - }, - "meta_launch_url": { - "type": "string", - "title": "Meta launch url" - }, - "meta_description": { - "type": "string", - "title": "Meta description" - }, - "meta_publisher": { - "type": "string", - "title": "Meta publisher" - }, - "policy_engine_mode": { - "type": "string", - "enum": [ - "all", - "any" - ], - "title": "Policy engine mode" - }, - "group": { - "type": "string", - "title": "Group" - } - }, - "required": [ - "name", - "slug" - ], - "title": "Application" + "single_use": { + "type": "boolean", + "title": "Single use", + "description": "When enabled, the invitation will be deleted after usage." }, - "permissions": { - "type": "string", - "minLength": 1, - "title": "Permissions" + "flow": { + "type": "integer", + "title": "Flow", + "description": "When set, only the configured flow can use this invitation." } }, "required": [] diff --git a/blueprints/system/providers-proxy.yaml b/blueprints/system/providers-proxy.yaml index 1214d157d..0086645a8 100644 --- a/blueprints/system/providers-proxy.yaml +++ b/blueprints/system/providers-proxy.yaml @@ -15,6 +15,7 @@ entries: # This mapping is used by the authentik proxy. It passes extra user attributes, # which are used for example for the HTTP-Basic Authentication mapping. return { + "sid": request.http_request.session.session_key, "ak_proxy": { "user_attributes": request.user.group_attributes(request), "is_superuser": request.user.is_superuser, diff --git a/go.mod b/go.mod index a154977b2..a0bd758e2 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module goauthentik.io -go 1.20 +go 1.21 require ( beryju.io/ldap v0.1.0 github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb github.com/coreos/go-oidc v2.2.1+incompatible - github.com/getsentry/sentry-go v0.24.1 + github.com/getsentry/sentry-go v0.25.0 github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/runtime v0.26.0 @@ -19,6 +19,7 @@ require ( github.com/gorilla/sessions v1.2.1 github.com/gorilla/websocket v1.5.0 github.com/jellydator/ttlcache/v3 v3.1.0 + github.com/mitchellh/mapstructure v1.5.0 github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 github.com/pires/go-proxyproto v0.7.0 github.com/prometheus/client_golang v1.17.0 @@ -26,10 +27,10 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 - goauthentik.io/api/v3 v3.2023083.4 + goauthentik.io/api/v3 v3.2023083.5 golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab - golang.org/x/oauth2 v0.12.0 - golang.org/x/sync v0.3.0 + golang.org/x/oauth2 v0.13.0 + golang.org/x/sync v0.4.0 gopkg.in/yaml.v2 v2.4.0 layeh.com/radius v0.0.0-20210819152912-ad72663a72ab ) @@ -60,7 +61,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -72,9 +72,9 @@ require ( go.mongodb.org/mongo-driver v1.11.3 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.16.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index d5a3b1b62..36bc70908 100644 --- a/go.sum +++ b/go.sum @@ -49,7 +49,9 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -73,11 +75,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/getsentry/sentry-go v0.24.1 h1:W6/0GyTy8J6ge6lVCc94WB6Gx2ZuLrgopnn9w8Hiwuk= -github.com/getsentry/sentry-go v0.24.1/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -196,6 +199,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -242,6 +246,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -269,6 +274,7 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -294,6 +300,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -343,11 +350,13 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -goauthentik.io/api/v3 v3.2023083.4 h1:WIi2+LFfBTvhxcbH/WqvhY/4EsX8bAN6mrPODq02B/w= -goauthentik.io/api/v3 v3.2023083.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +goauthentik.io/api/v3 v3.2023083.5 h1:U9/+QWIVpsfZCZivMAsG6E6dq3/4wT5qt/k7uUC9rZc= +goauthentik.io/api/v3 v3.2023083.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -359,8 +368,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -427,16 +437,16 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -449,8 +459,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -490,8 +500,9 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -645,6 +656,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= diff --git a/internal/outpost/ak/api.go b/internal/outpost/ak/api.go index cfdb41dd0..f6003c02f 100644 --- a/internal/outpost/ak/api.go +++ b/internal/outpost/ak/api.go @@ -22,6 +22,8 @@ import ( log "github.com/sirupsen/logrus" ) +type WSHandler func(ctx context.Context, args map[string]interface{}) + const ConfigLogLevel = "log_level" // APIController main controller which connects to the authentik api via http and ws @@ -42,6 +44,7 @@ type APIController struct { lastWsReconnect time.Time wsIsReconnecting bool wsBackoffMultiplier int + wsHandlers []WSHandler refreshHandlers []func() instanceUUID uuid.UUID @@ -106,6 +109,7 @@ func NewAPIController(akURL url.URL, token string) *APIController { reloadOffset: time.Duration(rand.Intn(10)) * time.Second, instanceUUID: uuid.New(), Outpost: outpost, + wsHandlers: []WSHandler{}, wsBackoffMultiplier: 1, refreshHandlers: make([]func(), 0), } @@ -156,6 +160,10 @@ func (a *APIController) AddRefreshHandler(handler func()) { a.refreshHandlers = append(a.refreshHandlers, handler) } +func (a *APIController) AddWSHandler(handler WSHandler) { + a.wsHandlers = append(a.wsHandlers, handler) +} + func (a *APIController) OnRefresh() error { // Because we don't know the outpost UUID, we simply do a list and pick the first // The service account this token belongs to should only have access to a single outpost diff --git a/internal/outpost/ak/api_ws.go b/internal/outpost/ak/api_ws.go index 681b26fa4..24c5099f4 100644 --- a/internal/outpost/ak/api_ws.go +++ b/internal/outpost/ak/api_ws.go @@ -1,6 +1,7 @@ package ak import ( + "context" "crypto/tls" "fmt" "net/http" @@ -145,6 +146,10 @@ func (ac *APIController) startWSHandler() { "build": constants.BUILD("tagged"), }).SetToCurrentTime() } + } else if wsMsg.Instruction == WebsocketInstructionProviderSpecific { + for _, h := range ac.wsHandlers { + h(context.Background(), wsMsg.Args) + } } } } diff --git a/internal/outpost/ak/api_ws_msg.go b/internal/outpost/ak/api_ws_msg.go index f1f2e3aa8..cedecb93d 100644 --- a/internal/outpost/ak/api_ws_msg.go +++ b/internal/outpost/ak/api_ws_msg.go @@ -9,6 +9,8 @@ const ( WebsocketInstructionHello websocketInstruction = 1 // WebsocketInstructionTriggerUpdate Code received to trigger a config update WebsocketInstructionTriggerUpdate websocketInstruction = 2 + // WebsocketInstructionProviderSpecific Code received to trigger some provider specific function + WebsocketInstructionProviderSpecific websocketInstruction = 3 ) type websocketMessage struct { diff --git a/internal/outpost/ldap/constants/constants.go b/internal/outpost/ldap/constants/constants.go index cfa85711e..a60db0e9f 100644 --- a/internal/outpost/ldap/constants/constants.go +++ b/internal/outpost/ldap/constants/constants.go @@ -25,6 +25,7 @@ const ( ) const ( + OCPerson = "person" OCUser = "user" OCOrgPerson = "organizationalPerson" OCInetOrgPerson = "inetOrgPerson" @@ -54,6 +55,8 @@ func GetContainerOCs() map[string]bool { func GetUserOCs() map[string]bool { return map[string]bool{ + OCTop: true, + OCPerson: true, OCUser: true, OCOrgPerson: true, OCInetOrgPerson: true, diff --git a/internal/outpost/ldap/entries.go b/internal/outpost/ldap/entries.go index 23bab252d..2236a9964 100644 --- a/internal/outpost/ldap/entries.go +++ b/internal/outpost/ldap/entries.go @@ -31,8 +31,8 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { u.Email = api.PtrString("") } attrs = utils.EnsureAttributes(attrs, map[string][]string{ - "ak-active": {strconv.FormatBool(*u.IsActive)}, - "ak-superuser": {strconv.FormatBool(u.IsSuperuser)}, + "ak-active": {strings.ToUpper(strconv.FormatBool(*u.IsActive))}, + "ak-superuser": {strings.ToUpper(strconv.FormatBool(u.IsSuperuser))}, "memberOf": pi.GroupsForUser(u), "cn": {u.Username}, "sAMAccountName": {u.Username}, @@ -41,11 +41,13 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { "displayName": {u.Name}, "mail": {*u.Email}, "objectClass": { - constants.OCUser, + constants.OCTop, + constants.OCPerson, constants.OCOrgPerson, constants.OCInetOrgPerson, - constants.OCAKUser, + constants.OCUser, constants.OCPosixAccount, + constants.OCAKUser, }, "uidNumber": {pi.GetUidNumber(u)}, "gidNumber": {pi.GetUidNumber(u)}, diff --git a/internal/outpost/ldap/search/direct/base.go b/internal/outpost/ldap/search/direct/base.go index 87eadd715..0f87b7bf3 100644 --- a/internal/outpost/ldap/search/direct/base.go +++ b/internal/outpost/ldap/search/direct/base.go @@ -33,6 +33,29 @@ func (ds *DirectSearcher) SearchBase(req *search.Request) (ldap.ServerSearchResu Name: "supportedLDAPVersion", Values: []string{"3"}, }, + { + Name: "supportedCapabilities", + Values: []string{ + "1.2.840.113556.1.4.800", //LDAP_CAP_ACTIVE_DIRECTORY_OID + "1.2.840.113556.1.4.1791", //LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID + "1.2.840.113556.1.4.1670", //LDAP_CAP_ACTIVE_DIRECTORY_V51_OID + "1.2.840.113556.1.4.1880", //LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST_OID + "1.2.840.113556.1.4.1851", //LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID + "1.2.840.113556.1.4.1920", //LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID + "1.2.840.113556.1.4.1935", //LDAP_CAP_ACTIVE_DIRECTORY_V60_OID + "1.2.840.113556.1.4.2080", //LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID + "1.2.840.113556.1.4.2237", //LDAP_CAP_ACTIVE_DIRECTORY_W8_OID + }, + }, + { + Name: "supportedControl", + Values: []string{ + "2.16.840.1.113730.3.4.9", //VLV Request LDAPv3 Control + "2.16.840.1.113730.3.4.10", //VLV Response LDAPv3 Control + "1.2.840.113556.1.4.474", //Sort result + "1.2.840.113556.1.4.319", //Paged Result Control + }, + }, { Name: "subschemaSubentry", Values: []string{"cn=subschema"}, diff --git a/internal/outpost/ldap/search/direct/schema.go b/internal/outpost/ldap/search/direct/schema.go index 81e601846..6fe88ead7 100644 --- a/internal/outpost/ldap/search/direct/schema.go +++ b/internal/outpost/ldap/search/direct/schema.go @@ -29,62 +29,80 @@ func (ds *DirectSearcher) SearchSubschema(req *search.Request) (ldap.ServerSearc }, }, { - Name: "objectClasses", + Name: "dITContentRules", Values: []string{ - "( 2.5.6.0 NAME 'top' ABSTRACT MUST ( objectClass ) MAY (cn $ description $ displayName $ memberOf $ name ) )", - "( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( cn ) MAY (sn $ telephoneNumber ) )", - "( 2.5.6.7 NAME 'organizationalPerson' SUP person STRUCTURAL MAY (c $ l $ o $ ou $ title $ givenName $ co $ department $ company $ division $ mail $ mobile $ telephoneNumber ) )", - "( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST (cn $ member ) MAY (o $ ou ) )", - "( 1.2.840.113556.1.5.9 NAME 'user' SUP organizationalPerson STRUCTURAL MAY ( name $ displayName $ uid $ mail ) )", - "( 1.3.6.1.1.1.2.0 NAME 'posixAccount' SUP top AUXILIARY MAY (cn $ description $ homeDirectory $ uid $ uidNumber $ gidNumber ) )", - "( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' AUX ( posixAccount ) MUST ( sAMAccountName ) MAY ( uidNumber $ gidNumber ))", - // Custom attributes - // Temporarily use 1.3.6.1.4.1.26027.1.1 as a base - // https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid - "( 1.3.6.1.4.1.26027.1.1.1 NAME 'goauthentik.io/ldap/user' SUP organizationalPerson STRUCTURAL MAY ( ak-active $ sAMAccountName $ goauthentikio-user-sources $ goauthentik.io/user/sources $ goauthentik.io/ldap/active $ goauthentik.io/ldap/superuser ) )", + "( 2.5.6.0 NAME 'top' )", + "( 2.5.6.6 NAME 'person' )", + "( 2.5.6.7 NAME 'organizationalPerson' )", + "( 2.5.6.9 NAME 'groupOfNames' )", + "( 1.2.840.113556.1.5.9 NAME 'user' )", + "( 1.3.6.1.1.1.2.0 NAME 'posixAccount' )", + "( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' )", + "( 1.3.6.1.4.1.26027.1.1.1 NAME 'goauthentik.io/ldap/user' )", }, }, { Name: "attributeTypes", Values: []string{ - "( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", - "( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts' DESC 'RFC4512: naming contexts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )", - "( 2.5.18.10 NAME 'subschemaSubentry' DESC 'RFC4512: name of controlling subschema entry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", - "( 1.3.6.1.4.1.1466.101.120.15 NAME 'supportedLDAPVersion' DESC 'RFC4512: supported LDAP versions' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation )", - "( 1.3.6.1.1.20 NAME 'entryDN' DESC 'DN of the entry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )", - "( 1.3.6.1.1.4 NAME 'vendorName' DESC 'RFC3045: name of implementation vendor' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", - "( 1.3.6.1.1.5 NAME 'vendorVersion' DESC 'RFC3045: version of implementation' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )", - "( 0.9.2342.19200300.100.1.1 NAME 'uid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 0.9.2342.19200300.100.1.3 NAME 'mail' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 0.9.2342.19200300.100.1.41 NAME 'mobile' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.2.102 NAME 'memberOf' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' NO-USER-MODIFICATION )", - "( 1.2.840.113556.1.2.13 NAME 'displayName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.4.1 NAME 'name' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE NO-USER-MODIFICATION )", - "( 1.2.840.113556.1.2.131 NAME 'co' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.2.141 NAME 'department' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.2.146 NAME 'company' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.4.44 NAME 'homeDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.4.221 NAME 'sAMAccountName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.2.840.113556.1.4.261 NAME 'division' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 1.3.6.1.1.1.1.0 NAME 'uidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )", - "( 1.3.6.1.1.1.1.1 NAME 'gidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )", + "( 2.5.4.0 NAME 'objectClass' SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' NO-USER-MODIFICATION )", + "( 2.5.4.4 NAME 'sn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 2.5.4.3 NAME 'cn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", "( 2.5.4.6 NAME 'c' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", "( 2.5.4.7 NAME 'l' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", "( 2.5.4.10 NAME 'o' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )", "( 2.5.4.11 NAME 'ou' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )", - "( 2.5.4.20 NAME 'telephoneNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 2.5.4.42 NAME 'givenName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 2.5.4.0 NAME 'objectClass' SYNTAX '1.3.6.1.4.1.1466.115.121.1.38' NO-USER-MODIFICATION )", - "( 2.5.4.3 NAME 'cn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", - "( 2.5.4.4 NAME 'sn' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", "( 2.5.4.12 NAME 'title' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", "( 2.5.4.13 NAME 'description' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )", + "( 2.5.4.20 NAME 'telephoneNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", "( 2.5.4.31 NAME 'member' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' )", + "( 2.5.4.42 NAME 'givenName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 2.5.21.2 NAME 'dITContentRules' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' NO-USER-MODIFICATION )", + "( 2.5.21.5 NAME 'attributeTypes' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' NO-USER-MODIFICATION )", + "( 2.5.21.6 NAME 'objectClasses' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' NO-USER-MODIFICATION )", + "( 0.9.2342.19200300.100.1.1 NAME 'uid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 0.9.2342.19200300.100.1.3 NAME 'mail' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 0.9.2342.19200300.100.1.41 NAME 'mobile' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.2.13 NAME 'displayName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.2.146 NAME 'company' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.2.102 NAME 'memberOf' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' NO-USER-MODIFICATION )", + "( 1.2.840.113556.1.2.131 NAME 'co' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.2.141 NAME 'department' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.4.1 NAME 'name' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE NO-USER-MODIFICATION )", + "( 1.2.840.113556.1.4.44 NAME 'homeDirectory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.4.221 NAME 'sAMAccountName' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.4.261 NAME 'division' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + "( 1.2.840.113556.1.4.750 NAME 'groupType' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )", + "( 1.2.840.113556.1.4.782 NAME 'objectCategory' SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.0 NAME 'uidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.1 NAME 'gidNumber' SYNTAX '1.3.6.1.4.1.1466.115.121.1.27' SINGLE-VALUE )", + "( 1.3.6.1.1.1.1.12 NAME 'memberUid' SYNTAX '1.3.6.1.4.1.1466.115.121.1.26' )", + // Custom attributes // Temporarily use 1.3.6.1.4.1.26027.1.1 as a base // https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid "( 1.3.6.1.4.1.26027.1.1.2 NAME ( 'goauthentik.io/ldap/superuser' 'ak-superuser' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )", "( 1.3.6.1.4.1.26027.1.1.3 NAME ( 'goauthentik.io/ldap/active' 'ak-active' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )", + "( 1.3.6.1.4.1.26027.1.1.4 NAME ( 'goauthentik.io/ldap/sources' 'goauthentikio-user-sources' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' SINGLE-VALUE )", + }, + }, + { + Name: "objectClasses", + Values: []string{ + "( 2.5.6.0 NAME 'top' ABSTRACT MUST ( objectClass ) MAY ( objectCategory $ cn $ description $ displayName $ memberOf $ name ) )", + "( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( cn ) MAY ( sn $ telephoneNumber ) )", + "( 2.5.6.7 NAME 'organizationalPerson' SUP person STRUCTURAL MAY ( c $ l $ o $ ou $ title $ givenName $ co $ department $ company $ division $ mail $ mobile $ telephoneNumber ) )", + "( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST ( cn $ member ) MAY ( o $ ou ) )", + "( 1.2.840.113556.1.5.9 NAME 'user' SUP organizationalPerson STRUCTURAL MAY ( name $ displayName $ uid $ mail ) )", + "( 1.3.6.1.1.1.2.0 NAME 'posixAccount' SUP top AUXILIARY MAY ( cn $ description $ homeDirectory $ uid $ uidNumber $ gidNumber ) )", + "( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' SUP user STRUCTURAL MAY ( uidNumber $ gidNumber $ displayName $ homeDirectory ) )", + "( 2.5.6.5 NAME 'organizationalUnit' SUP top STRUCTURAL MUST ( ou ) MAY ( c $ l ) )", + "( 1.2.840.113556.1.5.8 NAME 'group' SUP top AUXILIARY MAY ( cn $ groupType $ member ) )", + "( 1.3.6.1.1.1.2.2 NAME 'posixGroup' SUP top AUXILIARY MAY ( cn $ description $ gidNumber $ memberUid ) )", + "( 2.5.20.1 NAME 'subSchema' SUP top STRUCTURAL MAY ( dITContentRules $ attributeTypes $ objectClasses ) )", + // Custom attributes + // Temporarily use 1.3.6.1.4.1.26027.1.1 as a base + // https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid + "( 1.3.6.1.4.1.26027.1.1.1 NAME 'goauthentik.io/ldap/user' SUP organizationalPerson STRUCTURAL MAY ( ak-superuser $ ak-active $ sAMAccountName $ goauthentikio-user-sources $ goauthentik.io/user/sources $ goauthentik.io/ldap/active $ goauthentik.io/ldap/superuser ) )", }, }, }, diff --git a/internal/outpost/proxyv2/application/application.go b/internal/outpost/proxyv2/application/application.go index 657bcbec7..eae4c6774 100644 --- a/internal/outpost/proxyv2/application/application.go +++ b/internal/outpost/proxyv2/application/application.go @@ -280,7 +280,9 @@ func (a *Application) handleSignOut(rw http.ResponseWriter, r *http.Request) { "id_token_hint": []string{cc.RawToken}, } redirect += "?" + uv.Encode() - err = a.Logout(r.Context(), cc.Sub) + err = a.Logout(r.Context(), func(c Claims) bool { + return c.Sub == cc.Sub + }) if err != nil { a.log.WithError(err).Warning("failed to logout of other sessions") } diff --git a/internal/outpost/proxyv2/application/claims.go b/internal/outpost/proxyv2/application/claims.go index bd34e1309..32f4d26eb 100644 --- a/internal/outpost/proxyv2/application/claims.go +++ b/internal/outpost/proxyv2/application/claims.go @@ -11,10 +11,11 @@ type Claims struct { Exp int `json:"exp"` Email string `json:"email"` Verified bool `json:"email_verified"` - Proxy *ProxyClaims `json:"ak_proxy"` Name string `json:"name"` PreferredUsername string `json:"preferred_username"` Groups []string `json:"groups"` + Sid string `json:"sid"` + Proxy *ProxyClaims `json:"ak_proxy"` RawToken string } diff --git a/internal/outpost/proxyv2/application/session.go b/internal/outpost/proxyv2/application/session.go index 739b23e84..55d2bbb46 100644 --- a/internal/outpost/proxyv2/application/session.go +++ b/internal/outpost/proxyv2/application/session.go @@ -88,7 +88,7 @@ func (a *Application) getAllCodecs() []securecookie.Codec { return cs } -func (a *Application) Logout(ctx context.Context, sub string) error { +func (a *Application) Logout(ctx context.Context, filter func(c Claims) bool) error { if _, ok := a.sessions.(*sessions.FilesystemStore); ok { files, err := os.ReadDir(os.TempDir()) if err != nil { @@ -118,7 +118,7 @@ func (a *Application) Logout(ctx context.Context, sub string) error { continue } claims := s.Values[constants.SessionClaims].(Claims) - if claims.Sub == sub { + if filter(claims) { a.log.WithField("path", fullPath).Trace("deleting session") err := os.Remove(fullPath) if err != nil { @@ -153,7 +153,7 @@ func (a *Application) Logout(ctx context.Context, sub string) error { continue } claims := c.(Claims) - if claims.Sub == sub { + if filter(claims) { a.log.WithField("key", key).Trace("deleting session") _, err := client.Del(ctx, key).Result() if err != nil { diff --git a/internal/outpost/proxyv2/proxyv2.go b/internal/outpost/proxyv2/proxyv2.go index 154f79e34..70364957f 100644 --- a/internal/outpost/proxyv2/proxyv2.go +++ b/internal/outpost/proxyv2/proxyv2.go @@ -65,6 +65,7 @@ func NewProxyServer(ac *ak.APIController) *ProxyServer { globalMux.PathPrefix("/outpost.goauthentik.io/static").HandlerFunc(s.HandleStatic) globalMux.Path("/outpost.goauthentik.io/ping").HandlerFunc(sentryutils.SentryNoSample(s.HandlePing)) rootMux.PathPrefix("/").HandlerFunc(s.Handle) + ac.AddWSHandler(s.handleWSMessage) return s } diff --git a/internal/outpost/proxyv2/ws.go b/internal/outpost/proxyv2/ws.go new file mode 100644 index 000000000..b75ba50fd --- /dev/null +++ b/internal/outpost/proxyv2/ws.go @@ -0,0 +1,49 @@ +package proxyv2 + +import ( + "context" + + "github.com/mitchellh/mapstructure" + "goauthentik.io/internal/outpost/proxyv2/application" +) + +type WSProviderSubType string + +const ( + WSProviderSubTypeLogout WSProviderSubType = "logout" +) + +type WSProviderMsg struct { + SubType WSProviderSubType `mapstructure:"sub_type"` + SessionID string `mapstructure:"session_id"` +} + +func ParseWSProvider(args map[string]interface{}) (*WSProviderMsg, error) { + msg := &WSProviderMsg{} + err := mapstructure.Decode(args, &msg) + if err != nil { + return nil, err + } + return msg, nil +} + +func (ps *ProxyServer) handleWSMessage(ctx context.Context, args map[string]interface{}) { + msg, err := ParseWSProvider(args) + if err != nil { + ps.log.WithError(err).Warning("invalid provider-specific ws message") + return + } + switch msg.SubType { + case WSProviderSubTypeLogout: + for _, p := range ps.apps { + err := p.Logout(ctx, func(c application.Claims) bool { + return c.Sid == msg.SessionID + }) + if err != nil { + ps.log.WithField("provider", p.Host).WithError(err).Warning("failed to logout") + } + } + default: + ps.log.WithField("sub_type", msg.SubType).Warning("invalid sub_type") + } +} diff --git a/ldap.Dockerfile b/ldap.Dockerfile index 576a840ea..c43a2a77c 100644 --- a/ldap.Dockerfile +++ b/ldap.Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM docker.io/golang:1.21.1-bookworm AS builder +FROM docker.io/golang:1.21.2-bookworm AS builder WORKDIR /go/src/goauthentik.io diff --git a/lifecycle/migrate.py b/lifecycle/migrate.py index 8eee74adb..68d45dbc3 100755 --- a/lifecycle/migrate.py +++ b/lifecycle/migrate.py @@ -1,12 +1,12 @@ #!/usr/bin/env python """System Migration handler""" -import os from importlib.util import module_from_spec, spec_from_file_location from inspect import getmembers, isclass +from os import environ, system from pathlib import Path from typing import Any -from psycopg import connect +from psycopg import Connection, Cursor, connect from structlog.stdlib import get_logger from authentik.lib.config import CONFIG @@ -16,16 +16,33 @@ ADV_LOCK_UID = 1000 LOCKED = False +class CommandError(Exception): + """Error raised when a system_crit command fails""" + + class BaseMigration: """Base System Migration""" - cur: Any - con: Any + cur: Cursor + con: Connection def __init__(self, cur: Any, con: Any): self.cur = cur self.con = con + def system_crit(self, command: str): + """Run system command""" + LOGGER.debug("Running system_crit command", command=command) + retval = system(command) # nosec + if retval != 0: + raise CommandError("Migration error") + + def fake_migration(self, *app_migration: tuple[str, str]): + """Fake apply a list of migrations, arguments are + expected to be tuples of (app_label, migration_name)""" + for app, _migration in app_migration: + self.system_crit(f"./manage.py migrate {app} {_migration} --fake") + def needs_migration(self) -> bool: """Return true if Migration needs to be run""" return False @@ -82,7 +99,7 @@ if __name__ == "__main__": LOGGER.info("Migration finished applying", migration=sub) release_lock() LOGGER.info("applying django migrations") - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") + environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") wait_for_lock() try: from django.core.management import execute_from_command_line diff --git a/lifecycle/system_migrations/install_id.py b/lifecycle/system_migrations/install_id.py index 28867d8fa..2f66dcafc 100644 --- a/lifecycle/system_migrations/install_id.py +++ b/lifecycle/system_migrations/install_id.py @@ -20,18 +20,17 @@ class Migration(BaseMigration): def upgrade(self, migrate=False): self.cur.execute(SQL_STATEMENT) - self.con.commit() - if migrate: - # If we already have migrations in the database, assume we're upgrading an existing install - # and set the install id to the secret key - self.cur.execute( - "INSERT INTO authentik_install_id (id) VALUES (%s)", (CONFIG.get("secret_key"),) - ) - else: - # Otherwise assume a new install, generate an install ID based on a UUID - install_id = str(uuid4()) - self.cur.execute("INSERT INTO authentik_install_id (id) VALUES (%s)", (install_id,)) - self.con.commit() + with self.con.transaction(): + if migrate: + # If we already have migrations in the database, assume we're upgrading an existing install + # and set the install id to the secret key + self.cur.execute( + "INSERT INTO authentik_install_id (id) VALUES (%s)", (CONFIG.get("secret_key"),) + ) + else: + # Otherwise assume a new install, generate an install ID based on a UUID + install_id = str(uuid4()) + self.cur.execute("INSERT INTO authentik_install_id (id) VALUES (%s)", (install_id,)) def run(self): self.cur.execute( diff --git a/lifecycle/system_migrations/otp_merge.py b/lifecycle/system_migrations/otp_merge.py index c3908bffa..7561f0df7 100644 --- a/lifecycle/system_migrations/otp_merge.py +++ b/lifecycle/system_migrations/otp_merge.py @@ -1,10 +1,7 @@ # flake8: noqa -from os import system - from lifecycle.migrate import BaseMigration SQL_STATEMENT = """ -BEGIN TRANSACTION; DELETE FROM django_migrations WHERE app = 'otp_static'; DELETE FROM django_migrations WHERE app = 'otp_totp'; -- Rename tables (static) @@ -15,7 +12,7 @@ ALTER SEQUENCE otp_static_staticdevice_id_seq RENAME TO authentik_stages_authent -- Rename tables (totp) ALTER TABLE otp_totp_totpdevice RENAME TO authentik_stages_authenticator_totp_totpdevice; ALTER SEQUENCE otp_totp_totpdevice_id_seq RENAME TO authentik_stages_authenticator_totp_totpdevice_id_seq; -COMMIT;""" +""" class Migration(BaseMigration): @@ -25,23 +22,24 @@ class Migration(BaseMigration): ) return bool(self.cur.rowcount) - def system_crit(self, command): - retval = system(command) # nosec - if retval != 0: - raise Exception("Migration error") - def run(self): - self.cur.execute(SQL_STATEMENT) - self.con.commit() - self.system_crit( - "./manage.py migrate authentik_stages_authenticator_static 0008_initial --fake" - ) - self.system_crit( - "./manage.py migrate authentik_stages_authenticator_static 0009_throttling --fake" - ) - self.system_crit( - "./manage.py migrate authentik_stages_authenticator_totp 0008_initial --fake" - ) - self.system_crit( - "./manage.py migrate authentik_stages_authenticator_totp 0009_auto_20190420_0723 --fake" - ) + with self.con.transaction(): + self.cur.execute(SQL_STATEMENT) + self.fake_migration( + ( + "authentik_stages_authenticator_static", + "0008_initial", + ), + ( + "authentik_stages_authenticator_static", + "0009_throttling", + ), + ( + "authentik_stages_authenticator_totp", + "0008_initial", + ), + ( + "authentik_stages_authenticator_totp", + "0009_auto_20190420_0723", + ), + ) diff --git a/lifecycle/system_migrations/to_0_10.py b/lifecycle/system_migrations/to_0_10.py index 77ff3f69c..84ab45b39 100644 --- a/lifecycle/system_migrations/to_0_10.py +++ b/lifecycle/system_migrations/to_0_10.py @@ -1,10 +1,7 @@ # flake8: noqa -from os import system - from lifecycle.migrate import BaseMigration SQL_STATEMENT = """ -BEGIN TRANSACTION; DELETE FROM django_migrations WHERE app = 'passbook_stages_prompt'; DROP TABLE passbook_stages_prompt_prompt cascade; DROP TABLE passbook_stages_prompt_promptstage cascade; @@ -25,7 +22,7 @@ DELETE FROM django_migrations WHERE app = 'passbook_flows' AND name = '0008_defa DELETE FROM django_migrations WHERE app = 'passbook_flows' AND name = '0009_source_flows'; DELETE FROM django_migrations WHERE app = 'passbook_flows' AND name = '0010_provider_flows'; DELETE FROM django_migrations WHERE app = 'passbook_stages_password' AND name = '0002_passwordstage_change_flow'; -COMMIT;""" +""" class Migration(BaseMigration): @@ -35,17 +32,14 @@ class Migration(BaseMigration): ) return bool(self.cur.rowcount) - def system_crit(self, command): - retval = system(command) # nosec - if retval != 0: - raise Exception("Migration error") - def run(self): - self.cur.execute(SQL_STATEMENT) - self.con.commit() - self.system_crit("./manage.py migrate passbook_stages_prompt") - self.system_crit("./manage.py migrate passbook_flows 0008_default_flows --fake") - self.system_crit("./manage.py migrate passbook_flows 0009_source_flows --fake") - self.system_crit("./manage.py migrate passbook_flows 0010_provider_flows --fake") - self.system_crit("./manage.py migrate passbook_flows") - self.system_crit("./manage.py migrate passbook_stages_password --fake") + with self.con.transaction(): + self.cur.execute(SQL_STATEMENT) + self.system_crit("./manage.py migrate passbook_stages_prompt") + self.fake_migration( + ("passbook_flows", "0008_default_flows"), + ("passbook_flows", "0009_source_flows"), + ("passbook_flows", "0010_provider_flows"), + ) + self.system_crit("./manage.py migrate passbook_flows") + self.fake_migration(("passbook_stages_password", "")) diff --git a/lifecycle/system_migrations/to_0_13_authentik.py b/lifecycle/system_migrations/to_0_13_authentik.py index ff2088349..8ba702132 100644 --- a/lifecycle/system_migrations/to_0_13_authentik.py +++ b/lifecycle/system_migrations/to_0_13_authentik.py @@ -4,7 +4,7 @@ from redis import Redis from authentik.lib.config import CONFIG from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """BEGIN TRANSACTION; +SQL_STATEMENT = """ ALTER TABLE passbook_audit_event RENAME TO authentik_audit_event; ALTER TABLE passbook_core_application RENAME TO authentik_core_application; ALTER TABLE passbook_core_group RENAME TO authentik_core_group; @@ -92,8 +92,7 @@ ALTER SEQUENCE passbook_stages_prompt_promptstage_validation_policies_id_seq REN UPDATE django_migrations SET app = replace(app, 'passbook', 'authentik'); UPDATE django_content_type SET app_label = replace(app_label, 'passbook', 'authentik'); - -END TRANSACTION;""" +""" class Migration(BaseMigration): @@ -104,18 +103,18 @@ class Migration(BaseMigration): return bool(self.cur.rowcount) def run(self): - self.cur.execute(SQL_STATEMENT) - self.con.commit() - # We also need to clean the cache to make sure no pickeled objects still exist - for db in [ - CONFIG.get("redis.message_queue_db"), - CONFIG.get("redis.cache_db"), - CONFIG.get("redis.ws_db"), - ]: - redis = Redis( - host=CONFIG.get("redis.host"), - port=6379, - db=db, - password=CONFIG.get("redis.password"), - ) - redis.flushall() + with self.con.transaction(): + self.cur.execute(SQL_STATEMENT) + # We also need to clean the cache to make sure no pickeled objects still exist + for db in [ + CONFIG.get("redis.message_queue_db"), + CONFIG.get("redis.cache_db"), + CONFIG.get("redis.ws_db"), + ]: + redis = Redis( + host=CONFIG.get("redis.host"), + port=6379, + db=db, + password=CONFIG.get("redis.password"), + ) + redis.flushall() diff --git a/lifecycle/system_migrations/to_0_14_events..py b/lifecycle/system_migrations/to_0_14_events..py index 4745b8853..b1a0cc727 100644 --- a/lifecycle/system_migrations/to_0_14_events..py +++ b/lifecycle/system_migrations/to_0_14_events..py @@ -1,12 +1,9 @@ # flake8: noqa from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """BEGIN TRANSACTION; -ALTER TABLE authentik_audit_event RENAME TO authentik_events_event; +SQL_STATEMENT = """ALTER TABLE authentik_audit_event RENAME TO authentik_events_event; UPDATE django_migrations SET app = replace(app, 'authentik_audit', 'authentik_events'); -UPDATE django_content_type SET app_label = replace(app_label, 'authentik_audit', 'authentik_events'); - -END TRANSACTION;""" +UPDATE django_content_type SET app_label = replace(app_label, 'authentik_audit', 'authentik_events');""" class Migration(BaseMigration): @@ -17,5 +14,5 @@ class Migration(BaseMigration): return bool(self.cur.rowcount) def run(self): - self.cur.execute(SQL_STATEMENT) - self.con.commit() + with self.con.transaction(): + self.cur.execute(SQL_STATEMENT) diff --git a/lifecycle/system_migrations/to_2021_3_authenticator.py b/lifecycle/system_migrations/to_2021_3_authenticator.py index 3dd895bdb..3b633fef1 100644 --- a/lifecycle/system_migrations/to_2021_3_authenticator.py +++ b/lifecycle/system_migrations/to_2021_3_authenticator.py @@ -1,7 +1,7 @@ # flake8: noqa from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """BEGIN TRANSACTION; +SQL_STATEMENT = """ ALTER TABLE authentik_stages_otp_static_otpstaticstage RENAME TO authentik_stages_authenticator_static_otpstaticstage; UPDATE django_migrations SET app = replace(app, 'authentik_stages_otp_static', 'authentik_stages_authenticator_static'); UPDATE django_content_type SET app_label = replace(app_label, 'authentik_stages_otp_static', 'authentik_stages_authenticator_static'); @@ -13,8 +13,7 @@ UPDATE django_content_type SET app_label = replace(app_label, 'authentik_stages_ ALTER TABLE authentik_stages_otp_validate_otpvalidatestage RENAME TO authentik_stages_authenticator_validate_otpvalidatestage; UPDATE django_migrations SET app = replace(app, 'authentik_stages_otp_validate', 'authentik_stages_authenticator_validate'); UPDATE django_content_type SET app_label = replace(app_label, 'authentik_stages_otp_validate', 'authentik_stages_authenticator_validate'); - -END TRANSACTION;""" +""" class Migration(BaseMigration): @@ -26,5 +25,5 @@ class Migration(BaseMigration): return bool(self.cur.rowcount) def run(self): - self.cur.execute(SQL_STATEMENT) - self.con.commit() + with self.con.transaction(): + self.cur.execute(SQL_STATEMENT) diff --git a/lifecycle/system_migrations/to_2023_1_hibp_remove.py b/lifecycle/system_migrations/to_2023_1_hibp_remove.py index 4c8b9e292..c43f6bb85 100644 --- a/lifecycle/system_migrations/to_2023_1_hibp_remove.py +++ b/lifecycle/system_migrations/to_2023_1_hibp_remove.py @@ -1,10 +1,8 @@ # flake8: noqa from lifecycle.migrate import BaseMigration -SQL_STATEMENT = """BEGIN TRANSACTION; -DROP TABLE "authentik_policies_hibp_haveibeenpwendpolicy"; -DELETE FROM django_migrations WHERE app = 'authentik_policies_hibp'; -END TRANSACTION;""" +SQL_STATEMENT = """DROP TABLE "authentik_policies_hibp_haveibeenpwendpolicy"; +DELETE FROM django_migrations WHERE app = 'authentik_policies_hibp';""" class Migration(BaseMigration): @@ -16,5 +14,5 @@ class Migration(BaseMigration): return bool(self.cur.rowcount) def run(self): - self.cur.execute(SQL_STATEMENT) - self.con.commit() + with self.con.transaction(): + self.cur.execute(SQL_STATEMENT) diff --git a/poetry.lock b/poetry.lock index eabcf4729..78d92b40f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3723,20 +3723,19 @@ wsproto = ">=0.14" [[package]] name = "twilio" -version = "8.9.0" +version = "8.9.1" description = "Twilio API client and TwiML generator" optional = false python-versions = ">=3.7.0" files = [ - {file = "twilio-8.9.0-py2.py3-none-any.whl", hash = "sha256:d2f71060575432f73ed73f7cfc33259c53c96b92aa969d2910dacc8da1d1a5d9"}, - {file = "twilio-8.9.0.tar.gz", hash = "sha256:e711b5ea89694cb58a55a6b69f6190ea4c8499a1d6d68eb6a03c9840bb78fbb3"}, + {file = "twilio-8.9.1-py2.py3-none-any.whl", hash = "sha256:3edc0bcde7320b5ae5f516484af9092bc4df2f5a3b1d4d94a66c29310adb924c"}, + {file = "twilio-8.9.1.tar.gz", hash = "sha256:7bca5dc476d4d15e89e41d3074f8a265bd61445d1de9e8a697ca96cd8399eda6"}, ] [package.dependencies] aiohttp = ">=3.8.4" aiohttp-retry = ">=2.8.3" PyJWT = ">=2.0.0,<3.0.0" -pytz = "*" requests = ">=2.0.0" [[package]] diff --git a/proxy.Dockerfile b/proxy.Dockerfile index f6a226956..ffcde9122 100644 --- a/proxy.Dockerfile +++ b/proxy.Dockerfile @@ -15,7 +15,7 @@ COPY web . RUN npm run build-proxy # Stage 2: Build -FROM docker.io/golang:1.21.1-bookworm AS builder +FROM docker.io/golang:1.21.2-bookworm AS builder WORKDIR /go/src/goauthentik.io diff --git a/radius.Dockerfile b/radius.Dockerfile index 8b6d84900..d2faf4829 100644 --- a/radius.Dockerfile +++ b/radius.Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM docker.io/golang:1.21.1-bookworm AS builder +FROM docker.io/golang:1.21.2-bookworm AS builder WORKDIR /go/src/goauthentik.io diff --git a/schema.yml b/schema.yml index 5b682a7ff..03eed6d01 100644 --- a/schema.yml +++ b/schema.yml @@ -16292,6 +16292,10 @@ paths: schema: type: string format: uuid + - in: query + name: default_relay_state + schema: + type: string - in: query name: digest_algorithm schema: @@ -36303,6 +36307,9 @@ components: * `redirect` - Redirect * `post` - Post + default_relay_state: + type: string + description: Default relay_state value for IDP-initiated logins PatchedSAMLSourceRequest: type: object description: SAMLSource Serializer @@ -38480,6 +38487,9 @@ components: * `redirect` - Redirect * `post` - Post + default_relay_state: + type: string + description: Default relay_state value for IDP-initiated logins url_download_metadata: type: string description: Get metadata download URL @@ -38624,6 +38634,9 @@ components: * `redirect` - Redirect * `post` - Post + default_relay_state: + type: string + description: Default relay_state value for IDP-initiated logins required: - acs_url - authorization_flow @@ -40142,6 +40155,10 @@ components: type: string type: $ref: '#/components/schemas/UserTypeEnum' + uuid: + type: string + format: uuid + readOnly: true required: - avatar - groups_obj @@ -40150,6 +40167,7 @@ components: - pk - uid - username + - uuid UserAccountRequest: type: object description: Account adding/removing operations diff --git a/tests/e2e/test_provider_ldap.py b/tests/e2e/test_provider_ldap.py index 02564e4bd..aa12587ab 100644 --- a/tests/e2e/test_provider_ldap.py +++ b/tests/e2e/test_provider_ldap.py @@ -231,6 +231,7 @@ class TestProviderLDAP(SeleniumTestCase): for obj in response: del obj["raw_attributes"] del obj["raw_dn"] + obj["attributes"] = dict(obj["attributes"]) o_user = outpost.user expected = [ { @@ -244,11 +245,13 @@ class TestProviderLDAP(SeleniumTestCase): "sn": o_user.name, "mail": "", "objectClass": [ - "user", + "top", + "person", "organizationalPerson", "inetOrgPerson", - "goauthentik.io/ldap/user", + "user", "posixAccount", + "goauthentik.io/ldap/user", ], "uidNumber": 2000 + o_user.pk, "gidNumber": 2000 + o_user.pk, @@ -270,11 +273,13 @@ class TestProviderLDAP(SeleniumTestCase): "sn": embedded_account.name, "mail": "", "objectClass": [ - "user", + "top", + "person", "organizationalPerson", "inetOrgPerson", - "goauthentik.io/ldap/user", + "user", "posixAccount", + "goauthentik.io/ldap/user", ], "uidNumber": 2000 + embedded_account.pk, "gidNumber": 2000 + embedded_account.pk, @@ -296,11 +301,13 @@ class TestProviderLDAP(SeleniumTestCase): "sn": self.user.name, "mail": self.user.email, "objectClass": [ - "user", + "top", + "person", "organizationalPerson", "inetOrgPerson", - "goauthentik.io/ldap/user", + "user", "posixAccount", + "goauthentik.io/ldap/user", ], "uidNumber": 2000 + self.user.pk, "gidNumber": 2000 + self.user.pk, diff --git a/tests/wdio/package-lock.json b/tests/wdio/package-lock.json index 78a562d24..db3b5e383 100644 --- a/tests/wdio/package-lock.json +++ b/tests/wdio/package-lock.json @@ -9,11 +9,11 @@ "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", - "@wdio/cli": "^8.16.19", - "@wdio/local-runner": "^8.16.19", - "@wdio/mocha-framework": "^8.16.17", - "@wdio/spec-reporter": "^8.16.17", - "eslint": "^8.49.0", + "@wdio/cli": "^8.16.22", + "@wdio/local-runner": "^8.16.22", + "@wdio/mocha-framework": "^8.16.22", + "@wdio/spec-reporter": "^8.16.22", + "eslint": "^8.51.0", "eslint-config-google": "^0.14.0", "eslint-plugin-sonarjs": "^0.21.0", "npm-run-all": "^4.1.5", @@ -340,9 +340,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1067,18 +1067,18 @@ } }, "node_modules/@wdio/cli": { - "version": "8.16.19", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.16.19.tgz", - "integrity": "sha512-MGpRrb56kp0n+r/Z0KMe0o4O1dFgBhhjt/O4HC5l0WcPuf5Ew19w1Nr8PoSZ5XqXklGJi0woFh68YZA1MziInA==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.16.22.tgz", + "integrity": "sha512-/cv/fQ3qZoTJEnjmxWwPC2ohPfg5GrtBxi1MXNMJK85l1RHVYLkuNwUq18gSNcLol2vJ7GGoFowadlBjoCj56Q==", "dev": true, "dependencies": { "@types/node": "^20.1.1", - "@wdio/config": "8.16.17", - "@wdio/globals": "8.16.19", + "@wdio/config": "8.16.22", + "@wdio/globals": "8.16.22", "@wdio/logger": "8.16.17", "@wdio/protocols": "8.16.5", - "@wdio/types": "8.16.12", - "@wdio/utils": "8.16.17", + "@wdio/types": "8.16.22", + "@wdio/utils": "8.16.22", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", @@ -1093,7 +1093,7 @@ "lodash.union": "^4.6.0", "read-pkg-up": "10.1.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.16.19", + "webdriverio": "8.16.22", "yargs": "^17.7.2", "yarn-install": "^1.0.0" }, @@ -1117,14 +1117,14 @@ } }, "node_modules/@wdio/config": { - "version": "8.16.17", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.16.17.tgz", - "integrity": "sha512-9+AY73Dp6N/CHzUYe4KbYV8wcKh3mpzBsMKieNlwXi1bQ3AAirTjOXzQ2BoQn6fg/Yd1GxmT3F0YsVS+bF1PmQ==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.16.22.tgz", + "integrity": "sha512-sxqiVUEq++GFDKeR+HfzlgNhEbYuJZlrkU09p2rZzCRpPM3ty4azzdHB+XEdHHJJlV4UguvqUkp7n2126d9SAQ==", "dev": true, "dependencies": { "@wdio/logger": "8.16.17", - "@wdio/types": "8.16.12", - "@wdio/utils": "8.16.17", + "@wdio/types": "8.16.22", + "@wdio/utils": "8.16.22", "decamelize": "^6.0.0", "deepmerge-ts": "^5.0.0", "glob": "^10.2.2", @@ -1136,29 +1136,29 @@ } }, "node_modules/@wdio/globals": { - "version": "8.16.19", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.16.19.tgz", - "integrity": "sha512-KziZCYLcEvcsESJm2STkCEUKq2rhIbAP+1lyksULUdMsLoKhqW3yPrb8g6z3qj8G7yAYF8xWpKID0yQejl3UXA==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.16.22.tgz", + "integrity": "sha512-YGmGboSDTnFk+Bp/FJX2oPf548YILOr6M2T+wLnZtfgEPV5X8LbhT+XMqOYOiIdnI5MfgWGn8+XIgdjtNumHwQ==", "dev": true, "engines": { "node": "^16.13 || >=18" }, "optionalDependencies": { "expect-webdriverio": "^4.2.5", - "webdriverio": "8.16.19" + "webdriverio": "8.16.22" } }, "node_modules/@wdio/local-runner": { - "version": "8.16.19", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.16.19.tgz", - "integrity": "sha512-YUGF+7JCWoziFRW9/L+JSxuGKLgRiRXkRJ39iKaW97qS3MckBxLtuB4IY7gt3WJ80iDYo6IWLllRZfWtpvAT/A==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.16.22.tgz", + "integrity": "sha512-ZhP8lDgjYzBuopIROALMcAmjvo7KGYjk6W+eJAR2p1EgdQy8IFMIuYuK1lWhAGCN3GRMRC1CTVtEVxc79/EJMg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@wdio/logger": "8.16.17", "@wdio/repl": "8.10.1", - "@wdio/runner": "8.16.19", - "@wdio/types": "8.16.12", + "@wdio/runner": "8.16.22", + "@wdio/types": "8.16.22", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" @@ -1195,16 +1195,16 @@ } }, "node_modules/@wdio/mocha-framework": { - "version": "8.16.17", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.16.17.tgz", - "integrity": "sha512-aJ3CMzSBPOCb1i7hPyAsGYwccxPkD96qqdme/YUGL4U4SB+kEgDgNvouTJbyqvAB4VEuCcs+KqNWIMtM+rPi0Q==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.16.22.tgz", + "integrity": "sha512-/TgHCr4QoNRChGAXfBNZZHGEs+hMO2A8aU6mNEyrokAcHBoCL3NhnAP2SiQV0uT5wzoAOfv4RXqXOJ3bJa30Rw==", "dev": true, "dependencies": { "@types/mocha": "^10.0.0", "@types/node": "^20.1.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.16.12", - "@wdio/utils": "8.16.17", + "@wdio/types": "8.16.22", + "@wdio/utils": "8.16.22", "mocha": "^10.0.0" }, "engines": { @@ -1230,14 +1230,14 @@ } }, "node_modules/@wdio/reporter": { - "version": "8.16.17", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.16.17.tgz", - "integrity": "sha512-c7B4dnOhCM9qCn/0vlV0IjCTL/Dv++MNOMtZFTQlEEo5qXSX+LNkpsZi0STnkPqnv6ZP7liwz4bA01MFksGaww==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.16.22.tgz", + "integrity": "sha512-5f4H2bAaq+mxl51j+4pyDuhgvE5MIJOhF3G75AGCjEnXgDEHYJ+yzpvRwmLxPB98BkxZ/ldxrQth/I/l3+j1fQ==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.16.12", + "@wdio/types": "8.16.22", "diff": "^5.0.0", "object-inspect": "^1.12.0" }, @@ -1246,35 +1246,35 @@ } }, "node_modules/@wdio/runner": { - "version": "8.16.19", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.16.19.tgz", - "integrity": "sha512-CzLxlxcRfIVzTKGeo+TO5rUmmWHjUUdRaM6/6UGNFhFCcYW5rLvfHd1ojpU7ZKxBIc/bz2RGw2/cQ8KgQbFR3g==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.16.22.tgz", + "integrity": "sha512-SgWW1GPlZ7kS/7VZuuCYmCKIF6/WlOccGS2kHAg2rM45MjUId3KegNQ+INi2S3CkgU19M0rH2nNEUozp68dddw==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.16.17", - "@wdio/globals": "8.16.19", + "@wdio/config": "8.16.22", + "@wdio/globals": "8.16.22", "@wdio/logger": "8.16.17", - "@wdio/types": "8.16.12", - "@wdio/utils": "8.16.17", + "@wdio/types": "8.16.22", + "@wdio/utils": "8.16.22", "deepmerge-ts": "^5.0.0", "expect-webdriverio": "^4.2.5", "gaze": "^1.1.2", - "webdriver": "8.16.17", - "webdriverio": "8.16.19" + "webdriver": "8.16.22", + "webdriverio": "8.16.22" }, "engines": { "node": "^16.13 || >=18" } }, "node_modules/@wdio/spec-reporter": { - "version": "8.16.17", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.16.17.tgz", - "integrity": "sha512-CBpZhTJASDWpxJBUK5TLBZKBWbZxsVctpqXjpjG9fl9+IXBG00P5oFecDa90aUa00Dq+eIE1UUsVJa7evd36Tg==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.16.22.tgz", + "integrity": "sha512-bOELqVNDGRf4hxAtnYKjQiejMSKr/KNKgIwvyk6Ww2WHavzbZ/3oPRKNiyu5qgQR1lU+IN46V848EQV/RwtG+Q==", "dev": true, "dependencies": { - "@wdio/reporter": "8.16.17", - "@wdio/types": "8.16.12", + "@wdio/reporter": "8.16.22", + "@wdio/types": "8.16.22", "chalk": "^5.1.2", "easy-table": "^1.2.0", "pretty-ms": "^7.0.0" @@ -1296,9 +1296,9 @@ } }, "node_modules/@wdio/types": { - "version": "8.16.12", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.16.12.tgz", - "integrity": "sha512-TjCZJ3P9ual21G0dRv0lC9QgHGd3Igv+guEINevBKf/oD4/N84PvQ2eZG1nSbZ3xh8X/dvi+O64A6VEv43gx2w==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.16.22.tgz", + "integrity": "sha512-bg30seCgYu5JXukJ7M0qWKZLNATpKROvnl5/lRSOu4oopjm28UUan/+gHfHfyJ3MJ2uFNhaIVotPAvUziVJAdg==", "dev": true, "dependencies": { "@types/node": "^20.1.0" @@ -1308,14 +1308,14 @@ } }, "node_modules/@wdio/utils": { - "version": "8.16.17", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.16.17.tgz", - "integrity": "sha512-jDyOrxbQRDJO0OPt9UBgnwpUIKqtRn4+R0gR5VSDrIG/in5ZZg28yer8urrIVY4yY9ut5r/22VaMHZI9LEXF5w==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.16.22.tgz", + "integrity": "sha512-1hQjm7Jweiz+ABakS33TyWXYoxEg7LxL12RqbEYqtGB6ZTJhik+Cwyj/jcJbETSjiYJflmHxDvhFwuOkLR8ljg==", "dev": true, "dependencies": { "@puppeteer/browsers": "^1.6.0", "@wdio/logger": "8.16.17", - "@wdio/types": "8.16.12", + "@wdio/types": "8.16.22", "decamelize": "^6.0.0", "deepmerge-ts": "^5.1.0", "edgedriver": "^5.3.5", @@ -2595,11 +2595,10 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1188743", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1188743.tgz", - "integrity": "sha512-FZDQC58vLiGR2mjSgsMzU8aEJieovMonIyxf38b775eYdIfAYgSzyAWnDf0Eq6ouF/L9qcbqR8jcQeIC34jp/w==", - "dev": true, - "peer": true + "version": "0.0.1203626", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz", + "integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==", + "dev": true }, "node_modules/diff": { "version": "5.1.0", @@ -2946,15 +2945,15 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", + "@eslint/js": "8.51.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -8694,18 +8693,18 @@ } }, "node_modules/webdriver": { - "version": "8.16.17", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.17.tgz", - "integrity": "sha512-pG5aEqK6odI9Tr9pr0+1mN6iGqUu5uc5HTVbqbEM6CSX2g035JRVQ/tavFTegCF1HI6yIquHiwAqsfPgLciAnQ==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.22.tgz", + "integrity": "sha512-7W3LwQ5Np/qQG/EHD02aujv4QBuiE3/PbDd576s4QRDVa5RHLTvuAg3sZpj5kJ4wZEK2MbPsj1Nb8xqSyCUUqw==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.16.17", + "@wdio/config": "8.16.22", "@wdio/logger": "8.16.17", "@wdio/protocols": "8.16.5", - "@wdio/types": "8.16.12", - "@wdio/utils": "8.16.17", + "@wdio/types": "8.16.22", + "@wdio/utils": "8.16.22", "deepmerge-ts": "^5.1.0", "got": "^ 12.6.1", "ky": "^0.33.0", @@ -8753,18 +8752,18 @@ } }, "node_modules/webdriverio": { - "version": "8.16.19", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.16.19.tgz", - "integrity": "sha512-b63vRWWuLq7OKYTLMdCn+uvTW48sMFepEyrv8MKFJproaSOCcokw7sqJ/EcQFmqIgrZxKL/mDch+QKjxlW0ORw==", + "version": "8.16.22", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.16.22.tgz", + "integrity": "sha512-fzZtONvimqYc+C7DnnntkOz883+VP50uIvofLkDdH5yXU6duyclclChwWNZWllEHZvkLfpmLgrpgbS7R1wNGQg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.16.17", + "@wdio/config": "8.16.22", "@wdio/logger": "8.16.17", "@wdio/protocols": "8.16.5", "@wdio/repl": "8.10.1", - "@wdio/types": "8.16.12", - "@wdio/utils": "8.16.17", + "@wdio/types": "8.16.22", + "@wdio/utils": "8.16.22", "archiver": "^6.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", @@ -8781,7 +8780,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.16.17" + "webdriver": "8.16.22" }, "engines": { "node": "^16.13 || >=18" @@ -8804,12 +8803,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1203626", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1203626.tgz", - "integrity": "sha512-nEzHZteIUZfGCZtTiS1fRpC8UZmsfD1SiyPvaUNvS13dvKf666OAm8YTi0+Ca3n1nLEyu49Cy4+dPWpaHFJk9g==", - "dev": true - }, "node_modules/webdriverio/node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", diff --git a/tests/wdio/package.json b/tests/wdio/package.json index 2770bcca1..043f17643 100644 --- a/tests/wdio/package.json +++ b/tests/wdio/package.json @@ -6,11 +6,11 @@ "@trivago/prettier-plugin-sort-imports": "^4.2.0", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", - "@wdio/cli": "^8.16.19", - "@wdio/local-runner": "^8.16.19", - "@wdio/mocha-framework": "^8.16.17", - "@wdio/spec-reporter": "^8.16.17", - "eslint": "^8.49.0", + "@wdio/cli": "^8.16.22", + "@wdio/local-runner": "^8.16.22", + "@wdio/mocha-framework": "^8.16.22", + "@wdio/spec-reporter": "^8.16.22", + "eslint": "^8.51.0", "eslint-config-google": "^0.14.0", "eslint-plugin-sonarjs": "^0.21.0", "npm-run-all": "^4.1.5", diff --git a/web/package-lock.json b/web/package-lock.json index 19405122f..5a54dcfec 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -17,7 +17,7 @@ "@codemirror/theme-one-dark": "^6.1.2", "@formatjs/intl-listformat": "^7.4.2", "@fortawesome/fontawesome-free": "^6.4.2", - "@goauthentik/api": "^2023.8.3-1696335052", + "@goauthentik/api": "^2023.8.3-1696847703", "@lit-labs/context": "^0.4.1", "@lit-labs/task": "^3.0.2", "@lit/localize": "^0.11.4", @@ -55,12 +55,12 @@ "@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@lit/localize-tools": "^0.6.10", - "@rollup/plugin-babel": "^6.0.3", - "@rollup/plugin-commonjs": "^25.0.4", - "@rollup/plugin-node-resolve": "^15.2.1", - "@rollup/plugin-replace": "^5.0.2", - "@rollup/plugin-terser": "^0.4.3", - "@rollup/plugin-typescript": "^11.1.4", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^25.0.5", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.3", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.5", "@storybook/addon-essentials": "^7.4.6", "@storybook/addon-links": "^7.4.6", "@storybook/blocks": "^7.1.1", @@ -75,19 +75,19 @@ "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", "cross-env": "^7.0.3", - "eslint": "^8.50.0", + "eslint": "^8.51.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-lit": "^1.9.1", "eslint-plugin-sonarjs": "^0.21.0", - "eslint-plugin-storybook": "^0.6.14", + "eslint-plugin-storybook": "^0.6.15", "lit-analyzer": "^1.2.1", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", "pyright": "^1.1.330", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^3.29.4", + "rollup": "^4.0.2", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-postcss-lit": "^2.1.0", @@ -2796,9 +2796,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2882,9 +2882,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2023.8.3-1696335052", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.8.3-1696335052.tgz", - "integrity": "sha512-MFgMdkk8NVvJfgU9RfZlP8ypUjH5xhBtnannnFWDJLuvsYxyyaD6Rbj2cLE2KSaIGHEpbsmzeW3eUy7ZRjpKOw==" + "version": "2023.8.3-1696847703", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.8.3-1696847703.tgz", + "integrity": "sha512-RsOANX4L6RHaGXvMhJNq9g+E0ZLW3cwgl/t5CyQxLYvWgmVvZU4t78hxlOF7vFREoO5nhZUZnOOlD2+n5gOqLg==" }, "node_modules/@hcaptcha/types": { "version": "1.0.3", @@ -4345,9 +4345,9 @@ } }, "node_modules/@rollup/plugin-babel": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.3.tgz", - "integrity": "sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", + "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.18.6", @@ -4359,7 +4359,7 @@ "peerDependencies": { "@babel/core": "^7.0.0", "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "@types/babel__core": { @@ -4371,9 +4371,9 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz", - "integrity": "sha512-L92Vz9WUZXDnlQQl3EwbypJR4+DM2EbsO+/KOcEkP4Mc6Ct453EeDB2uH9lgRwj4w5yflgNpq9pHOiY8aoUXBQ==", + "version": "25.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.5.tgz", + "integrity": "sha512-xY8r/A9oisSeSuLCTfhssyDjo9Vp/eDiRLXkg1MXCcEEgEjPmLU+ZyDB20OOD0NlyDa/8SGbK5uIggF5XTx77w==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -4387,7 +4387,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" + "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -4396,9 +4396,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.1.tgz", - "integrity": "sha512-nsbUg588+GDSu8/NS8T4UAshO6xeaOfINNuXeVHcKV02LJtoRaM1SiOacClw4kws1SFiNhdLGxlbMY9ga/zs/w==", + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -4412,7 +4412,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" + "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -4421,9 +4421,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", - "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.3.tgz", + "integrity": "sha512-je7fu05B800IrMlWjb2wzJcdXzHYW46iTipfChnBDbIbDXhASZs27W1B58T2Yf45jZtJUONegpbce+9Ut2Ti/Q==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -4433,7 +4433,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -4442,9 +4442,9 @@ } }, "node_modules/@rollup/plugin-terser": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.3.tgz", - "integrity": "sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "dev": true, "dependencies": { "serialize-javascript": "^6.0.1", @@ -4455,7 +4455,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.x || ^3.x" + "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -4464,9 +4464,9 @@ } }, "node_modules/@rollup/plugin-typescript": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.4.tgz", - "integrity": "sha512-WZRh5LBVLQXdKFICUId5J3eIpmjGURaBqntfg3GSZACgeOAFS+lOSMGTwfzDkELTaZVp/lWdMVNU3UkwCUBg/Q==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz", + "integrity": "sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -4476,7 +4476,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.14.0||^3.0.0", + "rollup": "^2.14.0||^3.0.0||^4.0.0", "tslib": "*", "typescript": ">=3.7.0" }, @@ -4490,9 +4490,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", - "integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -4503,7 +4503,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -4511,6 +4511,162 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.0.2.tgz", + "integrity": "sha512-xDvk1pT4vaPU2BOLy0MqHMdYZyntqpaBf8RhBiezlqG9OjY8F50TyctHo8znigYKd+QCFhCmlmXHOL/LoaOl3w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.0.2.tgz", + "integrity": "sha512-lqCglytY3E6raze27DD9VQJWohbwCxzqs9aSHcj5X/8hJpzZfNdbsr4Ja9Hqp6iPyF53+5PtPx0pKRlkSvlHZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.0.2.tgz", + "integrity": "sha512-nkBKItS6E6CCzvRwgiKad+j+1ibmL7SIInj7oqMWmdkCjiSX6VeVZw2mLlRKIUL+JjsBgpATTfo7BiAXc1v0jA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.0.2.tgz", + "integrity": "sha512-vX2C8xvWPIbpEgQht95+dY6BReKAvtDgPDGi0XN0kWJKkm4WdNmq5dnwscv/zxvi+n6jUTBhs6GtpkkWT4q8Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.0.2.tgz", + "integrity": "sha512-DVFIfcHOjgmeHOAqji4xNz2wczt1Bmzy9MwBZKBa83SjBVO/i38VHDR+9ixo8QpBOiEagmNw12DucG+v55tCrg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.0.2.tgz", + "integrity": "sha512-GCK/a9ItUxPI0V5hQEJjH4JtOJO90GF2Hja7TO+EZ8rmkGvEi8/ZDMhXmcuDpQT7/PWrTT9RvnG8snMd5SrhBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.0.2.tgz", + "integrity": "sha512-cLuBp7rOjIB1R2j/VazjCmHC7liWUur2e9mFflLJBAWCkrZ+X0+QwHLvOQakIwDymungzAKv6W9kHZnTp/Mqrg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.0.2.tgz", + "integrity": "sha512-Zqw4iVnJr2naoyQus0yLy7sLtisCQcpdMKUCeXPBjkJtpiflRime/TMojbnl8O3oxUAj92mxr+t7im/RbgA20w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.0.2.tgz", + "integrity": "sha512-jJRU9TyUD/iMqjf8aLAp7XiN3pIj5v6Qcu+cdzBfVTKDD0Fvua4oUoK8eVJ9ZuKBEQKt3WdlcwJXFkpmMLk6kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.0.2.tgz", + "integrity": "sha512-ZkS2NixCxHKC4zbOnw64ztEGGDVIYP6nKkGBfOAxEPW71Sji9v8z3yaHNuae/JHPwXA+14oDefnOuVfxl59SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.0.2.tgz", + "integrity": "sha512-3SKjj+tvnZ0oZq2BKB+fI+DqYI83VrRzk7eed8tJkxeZ4zxJZcLSE8YDQLYGq1tZAnAX+H076RHHB4gTZXsQzw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.0.2.tgz", + "integrity": "sha512-MBdJIOxRauKkry7t2q+rTHa3aWjVez2eioWg+etRVS3dE4tChhmt5oqZYr48R6bPmcwEhxQr96gVRfeQrLbqng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sentry-internal/tracing": { "version": "7.73.0", "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.73.0.tgz", @@ -7500,6 +7656,22 @@ "node": ">=12" } }, + "node_modules/@storybook/builder-vite/node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/@storybook/channels": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.4.1.tgz", @@ -13606,15 +13778,15 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", + "@eslint/js": "8.51.0", "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -13710,9 +13882,9 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "0.6.14", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.14.tgz", - "integrity": "sha512-IeYigPur/MvESNDo43Z+Z5UvlcEVnt0dDZmnw1odi9X2Th1R3bpGyOZsHXb9bp1pFecOpRUuoMG5xdID2TwwOg==", + "version": "0.6.15", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.6.15.tgz", + "integrity": "sha512-lAGqVAJGob47Griu29KXYowI4G7KwMoJDOkEip8ujikuDLxU+oWJ1l0WL6F2oDO4QiyUFXvtDkEkISMOPzo+7w==", "dev": true, "dependencies": { "@storybook/csf": "^0.0.1", @@ -20233,18 +20405,30 @@ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.0.2.tgz", + "integrity": "sha512-MCScu4usMPCeVFaiLcgMDaBQeYi1z6vpWxz0r0hq0Hv77Y2YuOTZldkuNJ54BdYBH3e+nkrk6j0Rre/NLDBYzg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.0.2", + "@rollup/rollup-android-arm64": "4.0.2", + "@rollup/rollup-darwin-arm64": "4.0.2", + "@rollup/rollup-darwin-x64": "4.0.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.0.2", + "@rollup/rollup-linux-arm64-gnu": "4.0.2", + "@rollup/rollup-linux-arm64-musl": "4.0.2", + "@rollup/rollup-linux-x64-gnu": "4.0.2", + "@rollup/rollup-linux-x64-musl": "4.0.2", + "@rollup/rollup-win32-arm64-msvc": "4.0.2", + "@rollup/rollup-win32-ia32-msvc": "4.0.2", + "@rollup/rollup-win32-x64-msvc": "4.0.2", "fsevents": "~2.3.2" } }, @@ -23000,6 +23184,23 @@ } } }, + "node_modules/vite/node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "peer": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/vscode-css-languageservice": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-4.3.0.tgz", diff --git a/web/package.json b/web/package.json index 856e6d98b..08ede48ca 100644 --- a/web/package.json +++ b/web/package.json @@ -35,7 +35,7 @@ "@codemirror/theme-one-dark": "^6.1.2", "@formatjs/intl-listformat": "^7.4.2", "@fortawesome/fontawesome-free": "^6.4.2", - "@goauthentik/api": "^2023.8.3-1696335052", + "@goauthentik/api": "^2023.8.3-1696847703", "@lit-labs/context": "^0.4.1", "@lit-labs/task": "^3.0.2", "@lit/localize": "^0.11.4", @@ -73,12 +73,12 @@ "@jackfranklin/rollup-plugin-markdown": "^0.4.0", "@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@lit/localize-tools": "^0.6.10", - "@rollup/plugin-babel": "^6.0.3", - "@rollup/plugin-commonjs": "^25.0.4", - "@rollup/plugin-node-resolve": "^15.2.1", - "@rollup/plugin-replace": "^5.0.2", - "@rollup/plugin-terser": "^0.4.3", - "@rollup/plugin-typescript": "^11.1.4", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^25.0.5", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.3", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.5", "@storybook/addon-essentials": "^7.4.6", "@storybook/addon-links": "^7.4.6", "@storybook/blocks": "^7.1.1", @@ -93,19 +93,19 @@ "babel-plugin-macros": "^3.1.0", "babel-plugin-tsconfig-paths": "^1.0.3", "cross-env": "^7.0.3", - "eslint": "^8.50.0", + "eslint": "^8.51.0", "eslint-config-google": "^0.14.0", "eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-lit": "^1.9.1", "eslint-plugin-sonarjs": "^0.21.0", - "eslint-plugin-storybook": "^0.6.14", + "eslint-plugin-storybook": "^0.6.15", "lit-analyzer": "^1.2.1", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", "pyright": "^1.1.330", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^3.29.4", + "rollup": "^4.0.2", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-cssimport": "^1.0.3", "rollup-plugin-postcss-lit": "^2.1.0", diff --git a/web/src/admin/providers/saml/SAMLProviderForm.ts b/web/src/admin/providers/saml/SAMLProviderForm.ts index e74fa76b3..012abdcae 100644 --- a/web/src/admin/providers/saml/SAMLProviderForm.ts +++ b/web/src/admin/providers/saml/SAMLProviderForm.ts @@ -318,6 +318,24 @@ export class SAMLProviderFormPage extends ModelForm {

+ + +

+ ${msg( + "When using IDP-initiated logins, the relay state will be set to this value.", + )} +

+ +
Validate SSL Certificates of upstream servers. SSL-Zertifikate der Upstream-Server prüfen. - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Verwenden Sie diesen Provider mit auth_request von Nginx oder forwardAuth von Traefik. Jede Anwendung/Domäne benötigt ihren eigenen Provider. Zusätzlich muss auf jeder Domain /outpost.goauthentik.io an den Außenposten weitergeleitet werden (wenn Sie einen gemanagten Außenposten verwenden, wird dies für Sie erledigt). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. Verwenden Sie diesen Anbieter mit auth_request von nginx oder forwardAuth von traefik. Pro Root-Domain wird nur ein einziger Anbieter benötigt. Sie können keine Autorisierung pro Anwendung vornehmen, aber Sie müssen nicht für jede Anwendung einen Anbieter erstellen. @@ -5925,6 +5921,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/en.xlf b/web/xliff/en.xlf index 658eba084..66b8f5427 100644 --- a/web/xliff/en.xlf +++ b/web/xliff/en.xlf @@ -1041,10 +1041,6 @@ Validate SSL Certificates of upstream servers. Validate SSL Certificates of upstream servers. - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. @@ -6239,6 +6235,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/es.xlf b/web/xliff/es.xlf index cc46a33f8..f0995aeb7 100644 --- a/web/xliff/es.xlf +++ b/web/xliff/es.xlf @@ -976,10 +976,6 @@ Validate SSL Certificates of upstream servers. Validar los certificados SSL de los servidores ascendentes. - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Use este proveedor con auth_request de nginx o ForwardAuth de traefik. Cada aplicación/dominio necesita su propio proveedor. Además, en cada dominio, /outpost.goauthentik.io debe enrutarse al puesto avanzado (cuando se usa un puesto avanzado administrado, esto se hace por usted). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. Use este proveedor con auth_request de nginx o ForwardAuth de traefik. Solo se requiere un único proveedor por dominio raíz. No puede realizar la autorización por solicitud, pero no tiene que crear un proveedor para cada solicitud. @@ -5833,6 +5829,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/fr.xlf b/web/xliff/fr.xlf index 00909b58a..332d2ca4d 100644 --- a/web/xliff/fr.xlf +++ b/web/xliff/fr.xlf @@ -1295,11 +1295,6 @@ Il y a jour(s) Validate SSL Certificates of upstream servers. Valider les certificats SSL des serveurs amonts. - - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Utilisez ce fournisseur avec l'option "auth_request" de Nginx ou "forwardAuth" de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, "/outpost.goauthentik.io" doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. @@ -7816,6 +7811,15 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti WebAuthn not supported by browser. WebAuthn n'est pas supporté pas ce navigateur. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/pl.xlf b/web/xliff/pl.xlf index 99d097f04..b1456ad59 100644 --- a/web/xliff/pl.xlf +++ b/web/xliff/pl.xlf @@ -1002,10 +1002,6 @@ Validate SSL Certificates of upstream servers. Sprawdź poprawność certyfikatów SSL serwerów nadrzędnych. - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Użyj tego dostawcy z auth_request nginx lub forwardAuth traefik. Każda aplikacja/domena potrzebuje własnego dostawcy. Dodatkowo w każdej domenie /outpost.goauthentik.io musi być przekierowany do placówki (w przypadku korzystania z zarządzanej placówki jest to zrobione za Ciebie). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. Użyj tego dostawcy z auth_request nginx lub forwardAuth traefik. Tylko jeden dostawca jest wymagany na domenę główną. Nie możesz wykonać autoryzacji dla aplikacji, ale nie musisz tworzyć dostawcy dla każdej aplikacji. @@ -6072,6 +6068,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/pseudo-LOCALE.xlf b/web/xliff/pseudo-LOCALE.xlf index dd53b9b84..a14093264 100644 --- a/web/xliff/pseudo-LOCALE.xlf +++ b/web/xliff/pseudo-LOCALE.xlf @@ -1025,10 +1025,6 @@ Validate SSL Certificates of upstream servers. - - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. @@ -6174,6 +6170,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/tr.xlf b/web/xliff/tr.xlf index 34c746642..f304d5ae3 100644 --- a/web/xliff/tr.xlf +++ b/web/xliff/tr.xlf @@ -975,10 +975,6 @@ Validate SSL Certificates of upstream servers. Yayın yukarı akış sunucularının SSL Sertifikalarını doğrulayın. - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - Bu sağlayıcıyı nginx'in auth_request veya traefik's forwardAuth ile kullanın. Her uygulama/etki alanının kendi sağlayıcısına ihtiyacı vardır. Ayrıca, her etki alanında /outpost.goauthentik.io üsse yönlendirilmelidir (manged bir üs kullanırken, bu sizin için yapılır). - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. Bu sağlayıcıyı nginx'in auth_request veya traefik'in forwardAuth ile kullanın. Kök etki alanı başına yalnızca tek bir sağlayıcı gereklidir. Uygulama başına yetkilendirme yapamazsınız, ancak her uygulama için bir sağlayıcı oluşturmanız gerekmez. @@ -5826,6 +5822,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/zh-Hans.xlf b/web/xliff/zh-Hans.xlf index a760ca993..6fdf19dd8 100644 --- a/web/xliff/zh-Hans.xlf +++ b/web/xliff/zh-Hans.xlf @@ -1,4 +1,4 @@ - + @@ -613,9 +613,9 @@ - The URL "" was not found. - 未找到 URL " - "。 + The URL "" was not found. + 未找到 URL " + "。 @@ -1067,8 +1067,8 @@ - To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. - 要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。 + To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have. + 要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。 @@ -1295,11 +1295,6 @@ Validate SSL Certificates of upstream servers. 验证上游服务器的 SSL 证书。 - - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - 与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用此提供程序。每个应用程序/域名都需要自己的提供程序。此外,在每个域名上,/outpost.goauthentik.io 必须路由到前哨(在使用托管的 Outpost 时,这已经为您处理好了)。 - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. @@ -1814,8 +1809,8 @@ - Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". - 输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。 + Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test". + 输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。 @@ -3238,8 +3233,8 @@ doesn't pass when either or both of the selected options are equal or above the - Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' - 包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...' + Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...' + 包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...' @@ -4031,8 +4026,8 @@ doesn't pass when either or both of the selected options are equal or above the - When using an external logging solution for archiving, this can be set to "minutes=5". - 使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。 + When using an external logging solution for archiving, this can be set to "minutes=5". + 使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。 @@ -4041,8 +4036,8 @@ doesn't pass when either or both of the selected options are equal or above the - Format: "weeks=3;days=2;hours=3,seconds=2". - 格式:"weeks=3;days=2;hours=3,seconds=2"。 + Format: "weeks=3;days=2;hours=3,seconds=2". + 格式:"weeks=3;days=2;hours=3,seconds=2"。 @@ -4238,10 +4233,10 @@ doesn't pass when either or both of the selected options are equal or above the - Are you sure you want to update ""? + Are you sure you want to update ""? 您确定要更新 - " - " 吗? + " + " 吗? @@ -5342,7 +5337,7 @@ doesn't pass when either or both of the selected options are equal or above the - A "roaming" authenticator, like a YubiKey + A "roaming" authenticator, like a YubiKey 像 YubiKey 这样的“漫游”身份验证器 @@ -5677,10 +5672,10 @@ doesn't pass when either or both of the selected options are equal or above the - ("", of type ) + ("", of type ) - (" - ",类型为 + (" + ",类型为 @@ -5729,7 +5724,7 @@ doesn't pass when either or both of the selected options are equal or above the - If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. + If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here. 如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。 @@ -7818,7 +7813,19 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. 浏览器不支持 WebAuthn。 + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + 与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用此提供程序。每个应用程序/域名都需要自己的提供程序。此外,在每个域名上,/outpost.goauthentik.io 必须路由到前哨(在使用托管的 Outpost 时,这已经为您处理好了)。 + + + Default relay state + 默认中继状态 + + + When using IDP-initiated logins, the relay state will be set to this value. + 当使用 IDP 发起的登录时,中继状态会被设置为此值。 - + \ No newline at end of file diff --git a/web/xliff/zh-Hant.xlf b/web/xliff/zh-Hant.xlf index 2919040eb..3389edfd1 100644 --- a/web/xliff/zh-Hant.xlf +++ b/web/xliff/zh-Hant.xlf @@ -983,10 +983,6 @@ Validate SSL Certificates of upstream servers. 验证上游服务器的 SSL 证书。 - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - 将此提供程序与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用。每个应用程序/域都需要自己的提供商。此外,在每个域上,/outpost.goauthentik.io必须路由到 Outpost(使用托管的 Outpost 时,这是为您完成的)。 - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. 将此提供程序与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用。每个根域只需要一个提供程序。您无法执行每个应用程序的授权,但不必为每个应用程序创建提供程序。 @@ -5878,6 +5874,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/web/xliff/zh_CN.xlf b/web/xliff/zh_CN.xlf index acee9dc6b..411ef8e82 100644 --- a/web/xliff/zh_CN.xlf +++ b/web/xliff/zh_CN.xlf @@ -1295,11 +1295,6 @@ Validate SSL Certificates of upstream servers. 验证上游服务器的 SSL 证书。 - - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - 与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用此提供程序。每个应用程序/域名都需要自己的提供程序。此外,在每个域名上,/outpost.goauthentik.io 必须路由到前哨(在使用托管的 Outpost 时,这已经为您处理好了)。 - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. @@ -7818,6 +7813,18 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. 浏览器不支持 WebAuthn。 + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + 与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用此提供程序。每个应用程序/域名都需要自己的提供程序。此外,在每个域名上,/outpost.goauthentik.io 必须路由到前哨(在使用托管的 Outpost 时,这已经为您处理好了)。 + + + Default relay state + 默认中继状态 + + + When using IDP-initiated logins, the relay state will be set to this value. + 当使用 IDP 发起的登录时,中继状态会被设置为此值。 diff --git a/web/xliff/zh_TW.xlf b/web/xliff/zh_TW.xlf index a4c2ec374..8392c2a81 100644 --- a/web/xliff/zh_TW.xlf +++ b/web/xliff/zh_TW.xlf @@ -983,10 +983,6 @@ Validate SSL Certificates of upstream servers. 验证上游服务器的 SSL 证书。 - - Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a manged outpost, this is done for you). - 将此提供程序与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用。每个应用程序/域都需要自己的提供商。此外,在每个域上,/outpost.goauthentik.io必须路由到 Outpost(使用托管的 Outpost 时,这是为您完成的)。 - Use this provider with nginx's auth_request or traefik's forwardAuth. Only a single provider is required per root domain. You can't do per-application authorization, but you don't have to create a provider for each application. 将此提供程序与 nginx 的 auth_request 或 traefik 的 ForwardAuth 一起使用。每个根域只需要一个提供程序。您无法执行每个应用程序的授权,但不必为每个应用程序创建提供程序。 @@ -5877,6 +5873,15 @@ Bindings to groups/users are checked against the user of the event. WebAuthn not supported by browser. + + + Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you). + + + Default relay state + + + When using IDP-initiated logins, the relay state will be set to this value. diff --git a/website/blog/2023-10-05-SCIMs-many-deviations/image1.png b/website/blog/2023-10-05-SCIMs-many-deviations/image1.png new file mode 100644 index 000000000..cea8ca340 Binary files /dev/null and b/website/blog/2023-10-05-SCIMs-many-deviations/image1.png differ diff --git a/website/blog/2023-10-05-SCIMs-many-deviations/item.md b/website/blog/2023-10-05-SCIMs-many-deviations/item.md new file mode 100644 index 000000000..5b6656675 --- /dev/null +++ b/website/blog/2023-10-05-SCIMs-many-deviations/item.md @@ -0,0 +1,81 @@ +--- +title: "We need to talk about SCIM: More deviation than standard" +description: "SCIM’s many deviations, undocumented edge cases, and lack of official test coverage make it an especially complex protocol to implement." +slug: 2023-10-05-SCIMs-many-deviations +authors: + - name: Jens Langhammer + title: CTO at Authentik Security Inc + url: https://github.com/BeryJu + image_url: https://github.com/BeryJu.png +tags: + - SCIM + - SSO + - open source + - community + - identity provider + - security + - authentication +hide_table_of_contents: false +image: ./image1.png +--- + +> **_authentik is an open source Identity Provider that unifies your identity needs into a single platform, replacing Okta, Active Directory, and auth0. Authentik Security is a [public benefit company](https://github.com/OpenCoreVentures/ocv-public-benefit-company/blob/main/ocv-public-benefit-company-charter.md) building on top of the open source project._** + +--- + +As a young security company, we’ve been working on our implementation of SCIM (System for Cross-domain Identity Management), which I’ll share more about below. SCIM is in many ways a great improvement on LDAP, but we’ve run into challenges in implementation and some things just seem to be harder than they need to be. Is it just us? + +!["authentik admin interface"](./image1.png) + + + +# Improvements on LDAP + +From a security standpoint, it’s wise not to expose LDAP (Lightweight Directory Access Protocol) to the internet if you’re using Active Directory, OpenLDAP, FreeIPA or anything similar as your source of truth for authentication. SCIM fills a need for directory synchronization in a cloud-native world in which many companies aren’t hosting the software they use on their own servers. + +SCIM, being an HTTP API specification, is much simpler and (in theory) gives you less to worry about than LDAP (being its own specific protocol). SCIM also offers time- and cost-saving advantages over Just in Time provisioning, especially for scaling companies. SCIM can save hours of company time for IT admins who no longer have to manually create individual accounts across multiple applications for new team members. Offboarding is also streamlined as departing team members can be deprovisioned automatically, preventing unauthorized access. + +Most modern SaaS applications support SCIM, making it essential for security vendors to support the protocol, but it does come with its drawbacks. + +# Growing pains + +authentik currently supports SCIM going outwards; which means is that authentik is your source of truth/central directory, and you can use authentik together with a tool like [Sentry](https://sentry.io) that supports SCIM. In this case all your users or employees in authentik automatically get created in Sentry, with their correct group assignment, and they can just log in. + +Most of the information and commentary I see about SCIM focuses on the advantages described above, but I don’t see a lot of talk about the pitfalls of SCIM. I’m sharing our experiences here and am curious if others have found the same or can tell me how they’re avoiding these (I would love to hear that we’re doing this wrong actually!). + +## Deviation from standards isn’t well documented + +Implementing a protocol based on reading the RFCs and then writing the code is in itself not fun (to be fair, this is true for implementing any protocol based on a standard). Having implemented SCIM in line with the specification though, once we actually started testing with different solutions that can receive SCIM, we discovered a lot of quirks along the lines of x solution doesn’t do y (which the documentation says they should) or they do it slightly differently, and so on. + +This leads to a lot of workarounds which shouldn’t be necessary or things that simply don’t work without a clear cause. For example, when we started testing SCIM with Sentry, we ran into a lot of deviations (to their credit these were mostly listed in their [documentation](https://docs.sentry.io/product/accounts/sso/#scim-provisioning)). One of the issues I ran into when testing locally was when we created a user with SCIM, it just returned an error saying, “Please enter a valid email address” even though we _had_ sent it a valid email address. At least Sentry has the advantage of being open source, so we can just go and look at the code and see what’s happening, but this is still no small effort and you don’t have that option with closed source solutions. + +You can see other examples of confusing/unexpected behavior from SCIM [here](https://github.com/goauthentik/authentik/issues/5396) and [here](https://github.com/goauthentik/authentik/issues/6695). + +## Testing isn’t built out + +Some protocols make a big effort to uphold the adherence to the standard. OpenID Connect is another standard that’s well defined by multiple RFCs, but also has a lot of room for vendor-specific quirks. However, with OpenID we have the reassurance that the [OpenID Foundation](https://openid.net/foundation/) is behind it. + +The OpenID Foundation is a non-profit standards body of which Authentik Security is a member, but anyone can join to contribute to working groups that support implementation. OpenID Connect offers an [entire test suite](https://openid.net/certification/about-conformance-suite/) made up of hundreds of tests that you can run against your implementation, testing for edge cases and all the behaviors that they define. If you pass all the required tests you can send them the test results and get a [certification](https://openid.net/certification/) (which we are also working on) that your software adheres to the standards. + +Instead of working in the dark and trying to make sure you’ve interpreted the specs correctly (while testing with vendors who might have their own interpretations), you have some reassurance that you’re doing the right things when developing with OpenID Connect. + +To my knowledge there isn’t an official equivalent for SCIM—there are some smaller community projects that try to do something similar, but again, then you have to rely on someone’s interpretation of the standard. Even the [SCIM website’s overview page](https://scim.cloud/) says, “Information on this overview page is not normative.” + +## Updating a user is unnecessarily complex + +As mentioned above, authentik currently supports SCIM in one direction, but we are [working on making it so that another application can send SCIM to authentik](https://github.com/goauthentik/authentik/pull/3051), to create users in it. In this process we’ve discovered that updating a user is surprisingly annoying to implement. With SCIM [you have two options to update a user](https://datatracker.ietf.org/doc/html/rfc7644#autoid-22): + +- You can either send a request to replace the user (for which you have to send _all_ the user’s data), or +- You can send a patch request + +A lot of vendors use the patch request option to update group membership: they send a patch request for a user and just say, for example, “Add that group,” or “Remove that group.” This approach makes more sense in the case of an advanced user with tons of groups, as you’re not replacing everything, just making adjustments to their membership. However, this patch request is done with a custom filtering expression language which is extremely and needlessly complex. + +My first thought when I encountered this was, “Okay, can I just parse this with RegEx?” but it’s not possible. The correct way to parse it is with [ANTLR](https://www.antlr.org/), a parser generator for different kinds of grammars. The thing about ANTLR is that it’s a type of tool usually used to build a compiler: it allows you to define a grammar for which it generates a parser that can then parse things in said grammar. It’s not typically used for filtering language for directories and there are a lot of existing syntaxes that could have been used for this purpose. While luckily some people have written a full grammar for this, I was hoping that there would at least be an official definition for an ANTLR grammar. + +# Immaturity bites + +LDAP, being the more mature protocol (introduced in the ‘90s), has the advantage that deviations have been well documented and kinks ironed out. There are a handful of “standard” implementations like Active Directory, FreeIPA and some others. Similar to SAML support—there’s just been a lot more time to document edge cases and workarounds. + +SCIM, despite being around since 2015, is still subject to a lot of different interpretations of the standard, which leads to varying implementations and quirks with how vendors do SCIM. There’s a maturity challenge at work here in both senses—from the vendors but also from ourselves. Since we’ve added SCIM to our product a lot later than LDAP, there’s still a lot of room for us to catch up and make our implementation better. + +_Have you worked on SCIM implementation? Got advice for us? We’d love to hear from you in the comments._ diff --git a/website/docs/providers/scim/index.md b/website/docs/providers/scim/index.md index 1b47f919f..ee8f4c020 100644 --- a/website/docs/providers/scim/index.md +++ b/website/docs/providers/scim/index.md @@ -51,7 +51,7 @@ Applications can either match users on a unique ID sent by authentik called `ext #### OAuth/OIDC -The default provider configuration for the _Subject mode_ option of _Based on the User's hashed ID_ matches the `externalId` that's generated by default. If any other _Subjet mode_ is selected, the `externalId` attribute can be customized via SCIM mappings. +The default provider configuration for the _Subject mode_ option of _Based on the User's hashed ID_ matches the `externalId` that's generated by default. If any other _Subject mode_ is selected, the `externalId` attribute can be customized via SCIM mappings. #### SAML