Compare commits

..

10 Commits

Author SHA1 Message Date
27e9e892e7 web: update the type names for routes.
While writing up the commit message for the previous commit, I realized that I didn't really like
the typenames as they're outlaid in `routeUtils`.  This is much more explicit, describing the
four types of routes we have: ListRoute, ViewRoute, InternalRedirect, ExternalRedirect.
2024-03-14 14:40:19 -07:00
70bf745c0c web: remove a lot of duplication from the Routes
This commit extracts the highly repetitive `new Route(new RegExp(...))` syntax into a mappable
function, leaving only the regular expression and the asynchronous loader behind.  In the process,
it creates a typed description of what kinds of routes we have, and uses type-level pattern matching
to generate the route handlers and redirects.

For example, it establishes that for the purposes of discriminating between redirects and loaders,
the loader type is irrelevant to routing correctly. The loader type *is* still well-typed to prevent
anyone from putting an incompatible loader into a route, but the two different loader types-- ones
that take arguments, and ones that do not-- do not matter to the route builder.

Likewise, the two different kinds of redirects *do* matter, but only because JavaScript makes a
distinction between methods of one argument that can be `call`'ed and methods of more than one
argument that must be `apply`'d.

I suppose I could make this irrelevant by enforcing that the second argument to RawRedirect always
be an Array, but that's not ergonomic, and when the `match()` function has such a lovely and simple
way of distinguishing between the two forms, ergonomics always wins.

This might seem like a curious bit of cleanup, but by exposing the regular expressions as strings,
independent of the Routes that they ultimately represent, we get the power to associate paths to
routeable objects with paths to navigable objects in the Sidebar, *and* ultimately with paths to
navigable objects in a command palette. It might also lead to replacing our Routes infrastructure
with an off-the-shelf router such as [Vaadin.router](https://github.com/vaadin/router) or Justin
Fagnani's preferred [page.js](https://visionmedia.github.io/page.js/)
2024-03-14 14:19:41 -07:00
8b4e0361c4 Merge branch 'main' into dev
* main:
  web: clean up and remove redundant alias '@goauthentik/app' (#8889)
  web/admin: fix markdown table rendering (#8908)
2024-03-14 10:35:46 -07:00
22cb5b7379 Merge branch 'main' into dev
* main:
  web: bump chromedriver from 122.0.5 to 122.0.6 in /tests/wdio (#8902)
  web: bump vite-tsconfig-paths from 4.3.1 to 4.3.2 in /web (#8903)
  core: bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#8901)
  web: provide InstallID on EnterpriseListPage (#8898)
2024-03-14 08:14:43 -07:00
2d0117d096 Merge branch 'main' into dev
* main:
  api: capabilities: properly set can_save_media when s3 is enabled (#8896)
  web: bump the rollup group in /web with 3 updates (#8891)
  core: bump pydantic from 2.6.3 to 2.6.4 (#8892)
  core: bump twilio from 9.0.0 to 9.0.1 (#8893)
2024-03-13 14:05:11 -07:00
035bda4eac Merge branch 'main' into dev
* main:
  Update _envoy_istio.md (#8888)
  website/docs: new landing page for Providers (#8879)
  web: bump the sentry group in /web with 1 update (#8881)
  web: bump chromedriver from 122.0.4 to 122.0.5 in /tests/wdio (#8884)
  web: bump the eslint group in /tests/wdio with 2 updates (#8883)
  web: bump the eslint group in /web with 2 updates (#8885)
  website: bump @types/react from 18.2.64 to 18.2.65 in /website (#8886)
2024-03-12 13:31:35 -07:00
50906214e5 Merge branch 'main' into dev
* main:
  web: upgrade to lit 3 (#8781)
2024-03-11 11:03:04 -07:00
e505f274b6 Merge branch 'main' into dev
* main:
  web: fix esbuild issue with style sheets (#8856)
2024-03-11 10:28:05 -07:00
fe52f44dca Merge branch 'main' into dev
* main:
  tenants: really ensure default tenant cannot be deleted (#8875)
  core: bump github.com/go-openapi/runtime from 0.27.2 to 0.28.0 (#8867)
  core: bump pytest from 8.0.2 to 8.1.1 (#8868)
  core: bump github.com/go-openapi/strfmt from 0.22.2 to 0.23.0 (#8869)
  core: bump bandit from 1.7.7 to 1.7.8 (#8870)
  core: bump packaging from 23.2 to 24.0 (#8871)
  core: bump ruff from 0.3.1 to 0.3.2 (#8873)
  web: bump the wdio group in /tests/wdio with 3 updates (#8865)
  core: bump requests-oauthlib from 1.3.1 to 1.4.0 (#8866)
  core: bump uvicorn from 0.27.1 to 0.28.0 (#8872)
  core: bump django-filter from 23.5 to 24.1 (#8874)
2024-03-11 10:27:43 -07:00
3146e5a50f web: fix esbuild issue with style sheets
Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.
2024-03-08 14:15:55 -08:00
182 changed files with 2089 additions and 3934 deletions

View File

@ -7,6 +7,8 @@ on:
- main - main
- next - next
- version-* - version-*
paths-ignore:
- website/**
pull_request: pull_request:
branches: branches:
- main - main

View File

@ -10,7 +10,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from authentik import __version__, get_build_hash from authentik import __version__, get_build_hash
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
@ -19,7 +19,6 @@ class VersionSerializer(PassiveSerializer):
version_current = SerializerMethodField() version_current = SerializerMethodField()
version_latest = SerializerMethodField() version_latest = SerializerMethodField()
version_latest_valid = SerializerMethodField()
build_hash = SerializerMethodField() build_hash = SerializerMethodField()
outdated = SerializerMethodField() outdated = SerializerMethodField()
@ -39,10 +38,6 @@ class VersionSerializer(PassiveSerializer):
return __version__ return __version__
return version_in_cache return version_in_cache
def get_version_latest_valid(self, _) -> bool:
"""Check if latest version is valid"""
return cache.get(VERSION_CACHE_KEY) != VERSION_NULL
def get_outdated(self, instance) -> bool: def get_outdated(self, instance) -> bool:
"""Check if we're running the latest version""" """Check if we're running the latest version"""
return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance)) return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance))

View File

@ -18,7 +18,6 @@ from authentik.lib.utils.http import get_http_session
from authentik.root.celery import CELERY_APP from authentik.root.celery import CELERY_APP
LOGGER = get_logger() LOGGER = get_logger()
VERSION_NULL = "0.0.0"
VERSION_CACHE_KEY = "authentik_latest_version" VERSION_CACHE_KEY = "authentik_latest_version"
VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours
# Chop of the first ^ because we want to search the entire string # Chop of the first ^ because we want to search the entire string
@ -56,7 +55,7 @@ def clear_update_notifications():
def update_latest_version(self: SystemTask): def update_latest_version(self: SystemTask):
"""Update latest version info""" """Update latest version info"""
if CONFIG.get_bool("disable_update_check"): if CONFIG.get_bool("disable_update_check"):
cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
self.set_status(TaskStatus.WARNING, "Version check disabled.") self.set_status(TaskStatus.WARNING, "Version check disabled.")
return return
try: try:
@ -83,7 +82,7 @@ def update_latest_version(self: SystemTask):
event_dict["message"] = f"Changelog: {match.group()}" event_dict["message"] = f"Changelog: {match.group()}"
Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save() Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save()
except (RequestException, IndexError) as exc: except (RequestException, IndexError) as exc:
cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT) cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
self.set_error(exc) self.set_error(exc)

View File

@ -19,6 +19,8 @@ from guardian.models import UserObjectPermission
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.serializers import BaseSerializer, Serializer from rest_framework.serializers import BaseSerializer, Serializer
from structlog.stdlib import BoundLogger, get_logger from structlog.stdlib import BoundLogger, get_logger
from structlog.testing import capture_logs
from structlog.types import EventDict
from yaml import load from yaml import load
from authentik.blueprints.v1.common import ( from authentik.blueprints.v1.common import (
@ -40,7 +42,6 @@ from authentik.core.models import (
from authentik.enterprise.license import LicenseKey from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import LicenseUsage from authentik.enterprise.models import LicenseUsage
from authentik.enterprise.providers.rac.models import ConnectionToken from authentik.enterprise.providers.rac.models import ConnectionToken
from authentik.events.logs import LogEvent, capture_logs
from authentik.events.models import SystemTask from authentik.events.models import SystemTask
from authentik.events.utils import cleanse_dict from authentik.events.utils import cleanse_dict
from authentik.flows.models import FlowToken, Stage from authentik.flows.models import FlowToken, Stage
@ -160,7 +161,7 @@ class Importer:
def updater(value) -> Any: def updater(value) -> Any:
if value in self.__pk_map: if value in self.__pk_map:
self.logger.debug("Updating reference in entry", value=value) self.logger.debug("updating reference in entry", value=value)
return self.__pk_map[value] return self.__pk_map[value]
return value return value
@ -249,7 +250,7 @@ class Importer:
model_instance = existing_models.first() model_instance = existing_models.first()
if not isinstance(model(), BaseMetaModel) and model_instance: if not isinstance(model(), BaseMetaModel) and model_instance:
self.logger.debug( self.logger.debug(
"Initialise serializer with instance", "initialise serializer with instance",
model=model, model=model,
instance=model_instance, instance=model_instance,
pk=model_instance.pk, pk=model_instance.pk,
@ -259,14 +260,14 @@ class Importer:
elif model_instance and entry.state == BlueprintEntryDesiredState.MUST_CREATED: elif model_instance and entry.state == BlueprintEntryDesiredState.MUST_CREATED:
raise EntryInvalidError.from_entry( raise EntryInvalidError.from_entry(
( (
f"State is set to {BlueprintEntryDesiredState.MUST_CREATED} " f"state is set to {BlueprintEntryDesiredState.MUST_CREATED} "
"and object exists already", "and object exists already",
), ),
entry, entry,
) )
else: else:
self.logger.debug( self.logger.debug(
"Initialised new serializer instance", "initialised new serializer instance",
model=model, model=model,
**cleanse_dict(updated_identifiers), **cleanse_dict(updated_identifiers),
) )
@ -323,7 +324,7 @@ class Importer:
model: type[SerializerModel] = registry.get_model(model_app_label, model_name) model: type[SerializerModel] = registry.get_model(model_app_label, model_name)
except LookupError: except LookupError:
self.logger.warning( self.logger.warning(
"App or Model does not exist", app=model_app_label, model=model_name "app or model does not exist", app=model_app_label, model=model_name
) )
return False return False
# Validate each single entry # Validate each single entry
@ -335,7 +336,7 @@ class Importer:
if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT: if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT:
serializer = exc.serializer serializer = exc.serializer
else: else:
self.logger.warning(f"Entry invalid: {exc}", entry=entry, error=exc) self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
if raise_errors: if raise_errors:
raise exc raise exc
return False return False
@ -355,14 +356,14 @@ class Importer:
and state == BlueprintEntryDesiredState.CREATED and state == BlueprintEntryDesiredState.CREATED
): ):
self.logger.debug( self.logger.debug(
"Instance exists, skipping", "instance exists, skipping",
model=model, model=model,
instance=instance, instance=instance,
pk=instance.pk, pk=instance.pk,
) )
else: else:
instance = serializer.save() instance = serializer.save()
self.logger.debug("Updated model", model=instance) self.logger.debug("updated model", model=instance)
if "pk" in entry.identifiers: if "pk" in entry.identifiers:
self.__pk_map[entry.identifiers["pk"]] = instance.pk self.__pk_map[entry.identifiers["pk"]] = instance.pk
entry._state = BlueprintEntryState(instance) entry._state = BlueprintEntryState(instance)
@ -370,12 +371,12 @@ class Importer:
instance: Model | None = serializer.instance instance: Model | None = serializer.instance
if instance.pk: if instance.pk:
instance.delete() instance.delete()
self.logger.debug("Deleted model", mode=instance) self.logger.debug("deleted model", mode=instance)
continue continue
self.logger.debug("Entry to delete with no instance, skipping") self.logger.debug("entry to delete with no instance, skipping")
return True return True
def validate(self, raise_validation_errors=False) -> tuple[bool, list[LogEvent]]: def validate(self, raise_validation_errors=False) -> tuple[bool, list[EventDict]]:
"""Validate loaded blueprint export, ensure all models are allowed """Validate loaded blueprint export, ensure all models are allowed
and serializers have no errors""" and serializers have no errors"""
self.logger.debug("Starting blueprint import validation") self.logger.debug("Starting blueprint import validation")
@ -389,7 +390,9 @@ class Importer:
): ):
successful = self._apply_models(raise_errors=raise_validation_errors) successful = self._apply_models(raise_errors=raise_validation_errors)
if not successful: if not successful:
self.logger.warning("Blueprint validation failed") self.logger.debug("Blueprint validation failed")
for log in logs:
getattr(self.logger, log.get("log_level"))(**log)
self.logger.debug("Finished blueprint import validation") self.logger.debug("Finished blueprint import validation")
self._import = orig_import self._import = orig_import
return successful, logs return successful, logs

View File

@ -30,7 +30,6 @@ from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata, E
from authentik.blueprints.v1.importer import Importer from authentik.blueprints.v1.importer import Importer
from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE
from authentik.blueprints.v1.oci import OCI_PREFIX from authentik.blueprints.v1.oci import OCI_PREFIX
from authentik.events.logs import capture_logs
from authentik.events.models import TaskStatus from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask, prefill_task from authentik.events.system_tasks import SystemTask, prefill_task
from authentik.events.utils import sanitize_dict from authentik.events.utils import sanitize_dict
@ -212,14 +211,13 @@ def apply_blueprint(self: SystemTask, instance_pk: str):
if not valid: if not valid:
instance.status = BlueprintInstanceStatus.ERROR instance.status = BlueprintInstanceStatus.ERROR
instance.save() instance.save()
self.set_status(TaskStatus.ERROR, *logs) self.set_status(TaskStatus.ERROR, *[x["event"] for x in logs])
return return
with capture_logs() as logs:
applied = importer.apply() applied = importer.apply()
if not applied: if not applied:
instance.status = BlueprintInstanceStatus.ERROR instance.status = BlueprintInstanceStatus.ERROR
instance.save() instance.save()
self.set_status(TaskStatus.ERROR, *logs) self.set_status(TaskStatus.ERROR, "Failed to apply")
return return
instance.status = BlueprintInstanceStatus.SUCCESSFUL instance.status = BlueprintInstanceStatus.SUCCESSFUL
instance.last_applied_hash = file_hash instance.last_applied_hash = file_hash

View File

@ -20,14 +20,15 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik.admin.api.metrics import CoordinateSerializer from authentik.admin.api.metrics import CoordinateSerializer
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Application, User from authentik.core.models import Application, User
from authentik.events.logs import LogEventSerializer, capture_logs
from authentik.events.models import EventAction from authentik.events.models import EventAction
from authentik.events.utils import sanitize_dict
from authentik.lib.utils.file import ( from authentik.lib.utils.file import (
FilePathSerializer, FilePathSerializer,
FileUploadSerializer, FileUploadSerializer,
@ -36,7 +37,7 @@ from authentik.lib.utils.file import (
) )
from authentik.policies.api.exec import PolicyTestResultSerializer from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine from authentik.policies.engine import PolicyEngine
from authentik.policies.types import CACHE_PREFIX, PolicyResult from authentik.policies.types import PolicyResult
from authentik.rbac.decorators import permission_required from authentik.rbac.decorators import permission_required
from authentik.rbac.filters import ObjectFilter from authentik.rbac.filters import ObjectFilter
@ -45,7 +46,7 @@ LOGGER = get_logger()
def user_app_cache_key(user_pk: str) -> str: def user_app_cache_key(user_pk: str) -> str:
"""Cache key where application list for user is saved""" """Cache key where application list for user is saved"""
return f"{CACHE_PREFIX}/app_access/{user_pk}" return f"goauthentik.io/core/app_access/{user_pk}"
class ApplicationSerializer(ModelSerializer): class ApplicationSerializer(ModelSerializer):
@ -181,9 +182,9 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
if request.user.is_superuser: if request.user.is_superuser:
log_messages = [] log_messages = []
for log in logs: for log in logs:
if log.attributes.get("process", "") == "PolicyProcess": if log.get("process", "") == "PolicyProcess":
continue continue
log_messages.append(LogEventSerializer(log).data) log_messages.append(sanitize_dict(log))
result.log_messages = log_messages result.log_messages = log_messages
response = PolicyTestResultSerializer(result) response = PolicyTestResultSerializer(result)
return Response(response.data) return Response(response.data)
@ -213,7 +214,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
return super().list(request) return super().list(request)
queryset = self._filter_queryset_for_list(self.get_queryset()) queryset = self._filter_queryset_for_list(self.get_queryset())
paginated_apps = self.paginate_queryset(queryset) pagined_apps = self.paginate_queryset(queryset)
if "for_user" in request.query_params: if "for_user" in request.query_params:
try: try:
@ -227,18 +228,18 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
raise ValidationError({"for_user": "User not found"}) raise ValidationError({"for_user": "User not found"})
except ValueError as exc: except ValueError as exc:
raise ValidationError from exc raise ValidationError from exc
allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user) allowed_applications = self._get_allowed_applications(pagined_apps, user=for_user)
serializer = self.get_serializer(allowed_applications, many=True) serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data) return self.get_paginated_response(serializer.data)
allowed_applications = [] allowed_applications = []
if not should_cache: if not should_cache:
allowed_applications = self._get_allowed_applications(paginated_apps) allowed_applications = self._get_allowed_applications(pagined_apps)
if should_cache: if should_cache:
allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
if not allowed_applications: if not allowed_applications:
LOGGER.debug("Caching allowed application list") LOGGER.debug("Caching allowed application list")
allowed_applications = self._get_allowed_applications(paginated_apps) allowed_applications = self._get_allowed_applications(pagined_apps)
cache.set( cache.set(
user_app_cache_key(self.request.user.pk), user_app_cache_key(self.request.user.pk),
allowed_applications, allowed_applications,

View File

@ -617,9 +617,6 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
"""Get serializer for this model""" """Get serializer for this model"""
raise NotImplementedError raise NotImplementedError
def __str__(self) -> str:
return f"User-source connection (user={self.user.username}, source={self.source.slug})"
class Meta: class Meta:
unique_together = (("user", "source"),) unique_together = (("user", "source"),)

View File

@ -16,9 +16,8 @@ from authentik.core.models import Source, SourceUserMatchingModes, User, UserSou
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow, FlowToken, Stage, in_memory_stage from authentik.flows.models import Flow, Stage, in_memory_stage
from authentik.flows.planner import ( from authentik.flows.planner import (
PLAN_CONTEXT_IS_RESTORED,
PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_REDIRECT, PLAN_CONTEXT_REDIRECT,
PLAN_CONTEXT_SOURCE, PLAN_CONTEXT_SOURCE,
@ -36,8 +35,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.stage import PLAN_CONTEXT_USER_PATH from authentik.stages.user_write.stage import PLAN_CONTEXT_USER_PATH
SESSION_KEY_OVERRIDE_FLOW_TOKEN = "authentik/flows/source_override_flow_token" # nosec
class Action(Enum): class Action(Enum):
"""Actions that can be decided based on the request """Actions that can be decided based on the request
@ -225,43 +222,22 @@ class SourceFlowManager:
**kwargs, **kwargs,
) -> HttpResponse: ) -> HttpResponse:
"""Prepare Authentication Plan, redirect user FlowExecutor""" """Prepare Authentication Plan, redirect user FlowExecutor"""
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-user"
)
kwargs.update( kwargs.update(
{ {
# Since we authenticate the user by their token, they have no backend set # Since we authenticate the user by their token, they have no backend set
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT, PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SSO: True,
PLAN_CONTEXT_SOURCE: self.source, PLAN_CONTEXT_SOURCE: self.source,
PLAN_CONTEXT_REDIRECT: final_redirect,
PLAN_CONTEXT_SOURCES_CONNECTION: connection, PLAN_CONTEXT_SOURCES_CONNECTION: connection,
} }
) )
kwargs.update(self.policy_context) kwargs.update(self.policy_context)
if SESSION_KEY_OVERRIDE_FLOW_TOKEN in self.request.session:
token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
self._logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
plan = token.plan
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
plan.context.update(kwargs)
for stage in self.get_stages_to_append(flow):
plan.append_stage(stage)
if stages:
for stage in stages:
plan.append_stage(stage)
self.request.session[SESSION_KEY_PLAN] = plan
flow_slug = token.flow.slug
token.delete()
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=flow_slug,
)
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-user"
)
if PLAN_CONTEXT_REDIRECT not in kwargs:
kwargs[PLAN_CONTEXT_REDIRECT] = final_redirect
if not flow: if not flow:
return bad_request_message( return bad_request_message(
self.request, self.request,

View File

@ -6,13 +6,13 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.groups import GroupMemberSerializer from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.rac.api.endpoints import EndpointSerializer from authentik.enterprise.providers.rac.api.endpoints import EndpointSerializer
from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer
from authentik.enterprise.providers.rac.models import ConnectionToken from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint
class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer): class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer):
@ -23,7 +23,7 @@ class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer):
user = GroupMemberSerializer(source="session.user", read_only=True) user = GroupMemberSerializer(source="session.user", read_only=True)
class Meta: class Meta:
model = ConnectionToken model = Endpoint
fields = [ fields = [
"pk", "pk",
"provider", "provider",
@ -49,5 +49,5 @@ class ConnectionTokenViewSet(
filterset_fields = ["endpoint", "session__user", "provider"] filterset_fields = ["endpoint", "session__user", "provider"]
search_fields = ["endpoint__name", "provider__name"] search_fields = ["endpoint__name", "provider__name"]
ordering = ["endpoint__name", "provider__name"] ordering = ["endpoint__name", "provider__name"]
permission_classes = [OwnerSuperuserPermissions] permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]

View File

@ -15,7 +15,6 @@ CELERY_BEAT_SCHEDULE = {
TENANT_APPS = [ TENANT_APPS = [
"authentik.enterprise.audit", "authentik.enterprise.audit",
"authentik.enterprise.providers.rac", "authentik.enterprise.providers.rac",
"authentik.enterprise.stages.source",
] ]
MIDDLEWARE = ["authentik.enterprise.middleware.EnterpriseMiddleware"] MIDDLEWARE = ["authentik.enterprise.middleware.EnterpriseMiddleware"]

View File

@ -1,38 +0,0 @@
"""Source Stage API Views"""
from rest_framework.exceptions import ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Source
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.stages.source.models import SourceStage
from authentik.flows.api.stages import StageSerializer
class SourceStageSerializer(EnterpriseRequiredMixin, StageSerializer):
"""SourceStage Serializer"""
def validate_source(self, _source: Source) -> Source:
"""Ensure configured source supports web-based login"""
source = Source.objects.filter(pk=_source.pk).select_subclasses().first()
if not source:
raise ValidationError("Invalid source")
login_button = source.ui_login_button(self.context["request"])
if not login_button:
raise ValidationError("Invalid source selected, only web-based sources are supported.")
return source
class Meta:
model = SourceStage
fields = StageSerializer.Meta.fields + ["source", "resume_timeout"]
class SourceStageViewSet(UsedByMixin, ModelViewSet):
"""SourceStage Viewset"""
queryset = SourceStage.objects.all()
serializer_class = SourceStageSerializer
filterset_fields = "__all__"
ordering = ["name"]
search_fields = ["name"]

View File

@ -1,12 +0,0 @@
"""authentik stage app config"""
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikEnterpriseStageSourceConfig(EnterpriseConfig):
"""authentik source stage config"""
name = "authentik.enterprise.stages.source"
label = "authentik_stages_source"
verbose_name = "authentik Enterprise.Stages.Source"
default = True

View File

@ -1,53 +0,0 @@
# Generated by Django 5.0.2 on 2024-02-25 20:44
import authentik.lib.utils.time
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_core", "0033_alter_user_options"),
("authentik_flows", "0027_auto_20231028_1424"),
]
operations = [
migrations.CreateModel(
name="SourceStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
(
"resume_timeout",
models.TextField(
default="minutes=10",
help_text="Amount of time a user can take to return from the source to continue the flow (Format: hours=-1;minutes=-2;seconds=-3)",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"source",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_core.source"
),
),
],
options={
"verbose_name": "Source Stage",
"verbose_name_plural": "Source Stages",
},
bases=("authentik_flows.stage",),
),
]

View File

@ -1,45 +0,0 @@
"""Source stage models"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from authentik.flows.models import Stage
from authentik.lib.utils.time import timedelta_string_validator
class SourceStage(Stage):
"""Suspend the current flow execution and send the user to a source,
after which this flow execution is resumed."""
source = models.ForeignKey("authentik_core.Source", on_delete=models.CASCADE)
resume_timeout = models.TextField(
default="minutes=10",
validators=[timedelta_string_validator],
help_text=_(
"Amount of time a user can take to return from the source to continue the flow "
"(Format: hours=-1;minutes=-2;seconds=-3)"
),
)
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.stages.source.api import SourceStageSerializer
return SourceStageSerializer
@property
def view(self) -> type[View]:
from authentik.enterprise.stages.source.stage import SourceStageView
return SourceStageView
@property
def component(self) -> str:
return "ak-stage-source-form"
class Meta:
verbose_name = _("Source Stage")
verbose_name_plural = _("Source Stages")

View File

@ -1,79 +0,0 @@
"""Source stage logic"""
from typing import Any
from uuid import uuid4
from django.http import HttpRequest, HttpResponse
from django.utils.text import slugify
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from authentik.core.models import Source, User
from authentik.core.sources.flow_manager import SESSION_KEY_OVERRIDE_FLOW_TOKEN
from authentik.core.types import UILoginButton
from authentik.enterprise.stages.source.models import SourceStage
from authentik.flows.challenge import Challenge, ChallengeResponse
from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED
from authentik.flows.stage import ChallengeStageView
from authentik.lib.utils.time import timedelta_from_string
PLAN_CONTEXT_RESUME_TOKEN = "resume_token" # nosec
class SourceStageView(ChallengeStageView):
"""Suspend the current flow execution and send the user to a source,
after which this flow execution is resumed."""
login_button: UILoginButton
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
current_stage: SourceStage = self.executor.current_stage
source: Source = (
Source.objects.filter(pk=current_stage.source_id).select_subclasses().first()
)
if not source:
self.logger.warning("Source does not exist")
return self.executor.stage_invalid("Source does not exist")
self.login_button = source.ui_login_button(self.request)
if not self.login_button:
self.logger.warning("Source does not have a UI login button")
return self.executor.stage_invalid("Invalid source")
restore_token = self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED)
override_token = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
if restore_token and override_token and restore_token.pk == override_token.pk:
del self.request.session[SESSION_KEY_OVERRIDE_FLOW_TOKEN]
return self.executor.stage_ok()
return super().dispatch(request, *args, **kwargs)
def get_challenge(self, *args, **kwargs) -> Challenge:
resume_token = self.create_flow_token()
self.request.session[SESSION_KEY_OVERRIDE_FLOW_TOKEN] = resume_token
return self.login_button.challenge
def create_flow_token(self) -> FlowToken:
"""Save the current flow state in a token that can be used to resume this flow"""
pending_user: User = self.get_pending_user()
if pending_user.is_anonymous:
pending_user = get_anonymous_user()
current_stage: SourceStage = self.executor.current_stage
identifier = slugify(f"ak-source-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists
tokens = FlowToken.objects.filter(identifier=identifier)
valid_delta = timedelta_from_string(current_stage.resume_timeout)
if not tokens.exists():
return FlowToken.objects.create(
expires=now() + valid_delta,
user=pending_user,
identifier=identifier,
flow=self.executor.flow,
_plan=FlowToken.pickle(self.executor.plan),
)
token = tokens.first()
# Check if token is expired and rotate key if so
if token.is_expired:
token.expire_action()
return token
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()

View File

@ -1,99 +0,0 @@
"""Source stage tests"""
from django.urls import reverse
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.enterprise.stages.source.models import SourceStage
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.sources.saml.models import SAMLSource
from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
from authentik.stages.user_login.models import UserLoginStage
class TestSourceStage(FlowTestCase):
"""Source stage tests"""
def setUp(self):
self.source = SAMLSource.objects.create(
slug=generate_id(),
issuer="authentik",
allow_idp_initiated=True,
pre_authentication_flow=create_test_flow(),
)
def test_source_success(self):
"""Test"""
user = create_test_user()
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
stage = SourceStage.objects.create(name=generate_id(), source=self.source)
FlowStageBinding.objects.create(
target=flow,
stage=IdentificationStage.objects.create(
name=generate_id(),
user_fields=[UserFields.USERNAME],
),
order=0,
)
FlowStageBinding.objects.create(
target=flow,
stage=PasswordStage.objects.create(name=generate_id(), backends=[BACKEND_INBUILT]),
order=5,
)
FlowStageBinding.objects.create(target=flow, stage=stage, order=10)
FlowStageBinding.objects.create(
target=flow,
stage=UserLoginStage.objects.create(
name=generate_id(),
),
order=15,
)
# Get user identification stage
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-identification")
# Send username
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={"uid_field": user.username},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-password")
# Send password
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={"password": user.username},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(
response,
reverse("authentik_sources_saml:login", kwargs={"source_slug": self.source.slug}),
)
# Hijack flow plan so we don't have to emulate the source
flow_token = FlowToken.objects.filter(
identifier__startswith=f"ak-source-stage-{stage.name.lower()}"
).first()
self.assertIsNotNone(flow_token)
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token
session[SESSION_KEY_PLAN] = plan
session.save()
# Pretend we've just returned from the source
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))

View File

@ -1,5 +0,0 @@
"""API URLs"""
from authentik.enterprise.stages.source.api import SourceStageViewSet
api_urlpatterns = [("stages/source", SourceStageViewSet)]

View File

@ -12,6 +12,7 @@ from rest_framework.fields import (
ChoiceField, ChoiceField,
DateTimeField, DateTimeField,
FloatField, FloatField,
ListField,
SerializerMethodField, SerializerMethodField,
) )
from rest_framework.request import Request from rest_framework.request import Request
@ -20,7 +21,6 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework.viewsets import ReadOnlyModelViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.events.logs import LogEventSerializer
from authentik.events.models import SystemTask, TaskStatus from authentik.events.models import SystemTask, TaskStatus
from authentik.rbac.decorators import permission_required from authentik.rbac.decorators import permission_required
@ -39,7 +39,7 @@ class SystemTaskSerializer(ModelSerializer):
duration = FloatField(read_only=True) duration = FloatField(read_only=True)
status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus]) status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus])
messages = LogEventSerializer(many=True) messages = ListField(child=CharField())
def get_full_name(self, instance: SystemTask) -> str: def get_full_name(self, instance: SystemTask) -> str:
"""Get full name with UID""" """Get full name with UID"""

View File

@ -1,82 +0,0 @@
from collections.abc import Generator
from contextlib import contextmanager
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
from django.utils.timezone import now
from rest_framework.fields import CharField, ChoiceField, DateTimeField, DictField
from structlog import configure, get_config
from structlog.stdlib import NAME_TO_LEVEL, ProcessorFormatter
from structlog.testing import LogCapture
from structlog.types import EventDict
from authentik.core.api.utils import PassiveSerializer
from authentik.events.utils import sanitize_dict
@dataclass()
class LogEvent:
event: str
log_level: str
logger: str
timestamp: datetime = field(default_factory=now)
attributes: dict[str, Any] = field(default_factory=dict)
@staticmethod
def from_event_dict(item: EventDict) -> "LogEvent":
event = item.pop("event")
log_level = item.pop("level").lower()
timestamp = datetime.fromisoformat(item.pop("timestamp"))
item.pop("pid", None)
# Sometimes log entries have both `level` and `log_level` set, but `level` is always set
item.pop("log_level", None)
return LogEvent(
event, log_level, item.pop("logger"), timestamp, attributes=sanitize_dict(item)
)
class LogEventSerializer(PassiveSerializer):
"""Single log message with all context logged."""
timestamp = DateTimeField()
log_level = ChoiceField(choices=tuple((x, x) for x in NAME_TO_LEVEL.keys()))
logger = CharField()
event = CharField()
attributes = DictField()
# TODO(2024.6?): This is a migration helper to return a correct API response for logs that
# have been saved in an older format (mostly just list[str] with just the messages)
def to_representation(self, instance):
if isinstance(instance, str):
instance = LogEvent(instance, "", "")
elif isinstance(instance, list):
instance = [LogEvent(x, "", "") for x in instance]
return super().to_representation(instance)
@contextmanager
def capture_logs(log_default_output=True) -> Generator[list[LogEvent], None, None]:
"""Capture log entries created"""
logs = []
cap = LogCapture()
# Modify `_Configuration.default_processors` set via `configure` but always
# keep the list instance intact to not break references held by bound
# loggers.
processors: list = get_config()["processors"]
old_processors = processors.copy()
try:
# clear processors list and use LogCapture for testing
if ProcessorFormatter.wrap_for_formatter in processors:
processors.remove(ProcessorFormatter.wrap_for_formatter)
processors.append(cap)
configure(processors=processors)
yield logs
for raw_log in cap.entries:
logs.append(LogEvent.from_event_dict(raw_log))
finally:
# remove LogCapture and restore original processors
processors.clear()
processors.extend(old_processors)
configure(processors=processors)

View File

@ -452,13 +452,6 @@ class NotificationTransport(SerializerModel):
def send_email(self, notification: "Notification") -> list[str]: def send_email(self, notification: "Notification") -> list[str]:
"""Send notification via global email configuration""" """Send notification via global email configuration"""
if notification.user.email.strip() == "":
LOGGER.info(
"Discarding notification as user has no email address",
user=notification.user,
notification=notification,
)
return None
subject_prefix = "authentik Notification: " subject_prefix = "authentik Notification: "
context = { context = {
"key_value": { "key_value": {

View File

@ -9,7 +9,6 @@ from django.utils.translation import gettext_lazy as _
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from tenant_schemas_celery.task import TenantTask from tenant_schemas_celery.task import TenantTask
from authentik.events.logs import LogEvent
from authentik.events.models import Event, EventAction, TaskStatus from authentik.events.models import Event, EventAction, TaskStatus
from authentik.events.models import SystemTask as DBSystemTask from authentik.events.models import SystemTask as DBSystemTask
from authentik.events.utils import sanitize_item from authentik.events.utils import sanitize_item
@ -25,7 +24,7 @@ class SystemTask(TenantTask):
save_on_success: bool save_on_success: bool
_status: TaskStatus _status: TaskStatus
_messages: list[LogEvent] _messages: list[str]
_uid: str | None _uid: str | None
# Precise start time from perf_counter # Precise start time from perf_counter
@ -45,20 +44,15 @@ class SystemTask(TenantTask):
"""Set UID, so in the case of an unexpected error its saved correctly""" """Set UID, so in the case of an unexpected error its saved correctly"""
self._uid = uid self._uid = uid
def set_status(self, status: TaskStatus, *messages: LogEvent): def set_status(self, status: TaskStatus, *messages: str):
"""Set result for current run, will overwrite previous result.""" """Set result for current run, will overwrite previous result."""
self._status = status self._status = status
self._messages = list(messages) self._messages = messages
for idx, msg in enumerate(self._messages):
if not isinstance(msg, LogEvent):
self._messages[idx] = LogEvent(msg, logger=self.__name__, log_level="info")
def set_error(self, exception: Exception): def set_error(self, exception: Exception):
"""Set result to error and save exception""" """Set result to error and save exception"""
self._status = TaskStatus.ERROR self._status = TaskStatus.ERROR
self._messages = [ self._messages = [exception_to_string(exception)]
LogEvent(exception_to_string(exception), logger=self.__name__, log_level="error")
]
def before_start(self, task_id, args, kwargs): def before_start(self, task_id, args, kwargs):
self._start_precise = perf_counter() self._start_precise = perf_counter()
@ -104,7 +98,8 @@ class SystemTask(TenantTask):
def on_failure(self, exc, task_id, args, kwargs, einfo): def on_failure(self, exc, task_id, args, kwargs, einfo):
super().on_failure(exc, task_id, args, kwargs, einfo=einfo) super().on_failure(exc, task_id, args, kwargs, einfo=einfo)
if not self._status: if not self._status:
self.set_error(exc) self._status = TaskStatus.ERROR
self._messages = exception_to_string(exc)
DBSystemTask.objects.update_or_create( DBSystemTask.objects.update_or_create(
name=self.__name__, name=self.__name__,
uid=self._uid, uid=self._uid,

View File

@ -47,4 +47,3 @@ class FlowStageBindingViewSet(UsedByMixin, ModelViewSet):
filterset_fields = "__all__" filterset_fields = "__all__"
search_fields = ["stage__name"] search_fields = ["stage__name"]
ordering = ["order"] ordering = ["order"]
ordering_fields = ["order", "stage__name"]

View File

@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, ReadOnlyField from rest_framework.fields import BooleanField, CharField, DictField, ListField, ReadOnlyField
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -19,7 +19,7 @@ from authentik.blueprints.v1.exporter import FlowExporter
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, Importer from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, Importer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import CacheSerializer, LinkSerializer, PassiveSerializer from authentik.core.api.utils import CacheSerializer, LinkSerializer, PassiveSerializer
from authentik.events.logs import LogEventSerializer from authentik.events.utils import sanitize_dict
from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer from authentik.flows.api.flows_diagram import FlowDiagram, FlowDiagramSerializer
from authentik.flows.exceptions import FlowNonApplicableException from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow from authentik.flows.models import Flow
@ -107,7 +107,7 @@ class FlowSetSerializer(FlowSerializer):
class FlowImportResultSerializer(PassiveSerializer): class FlowImportResultSerializer(PassiveSerializer):
"""Logs of an attempted flow import""" """Logs of an attempted flow import"""
logs = LogEventSerializer(many=True, read_only=True) logs = ListField(child=DictField(), read_only=True)
success = BooleanField(read_only=True) success = BooleanField(read_only=True)
@ -184,7 +184,7 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
importer = Importer.from_string(file.read().decode()) importer = Importer.from_string(file.read().decode())
valid, logs = importer.validate() valid, logs = importer.validate()
import_response.initial_data["logs"] = [LogEventSerializer(log).data for log in logs] import_response.initial_data["logs"] = [sanitize_dict(log) for log in logs]
import_response.initial_data["success"] = valid import_response.initial_data["success"] = valid
import_response.is_valid() import_response.is_valid()
if not valid: if not valid:

View File

@ -13,7 +13,6 @@ from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.types import UserSettingSerializer from authentik.core.types import UserSettingSerializer
from authentik.enterprise.apps import EnterpriseConfig
from authentik.flows.api.flows import FlowSetSerializer from authentik.flows.api.flows import FlowSetSerializer
from authentik.flows.models import ConfigurableStage, Stage from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.reflection import all_subclasses
@ -76,7 +75,6 @@ class StageViewSet(
"description": subclass.__doc__, "description": subclass.__doc__,
"component": subclass().component, "component": subclass().component,
"model_name": subclass._meta.model_name, "model_name": subclass._meta.model_name,
"requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig),
} }
) )
data = sorted(data, key=lambda x: x["name"]) data = sorted(data, key=lambda x: x["name"])

View File

@ -59,11 +59,11 @@ class FlowPlan:
markers: list[StageMarker] = field(default_factory=list) markers: list[StageMarker] = field(default_factory=list)
def append_stage(self, stage: Stage, marker: StageMarker | None = None): def append_stage(self, stage: Stage, marker: StageMarker | None = None):
"""Append `stage` to the end of the plan, optionally with stage marker""" """Append `stage` to all stages, optionally with stage marker"""
return self.append(FlowStageBinding(stage=stage), marker) return self.append(FlowStageBinding(stage=stage), marker)
def append(self, binding: FlowStageBinding, marker: StageMarker | None = None): def append(self, binding: FlowStageBinding, marker: StageMarker | None = None):
"""Append `stage` to the end of the plan, optionally with stage marker""" """Append `stage` to all stages, optionally with stage marker"""
self.bindings.append(binding) self.bindings.append(binding)
self.markers.append(marker or StageMarker()) self.markers.append(marker or StageMarker())

View File

@ -53,7 +53,6 @@ class TestFlowInspector(APITestCase):
"title": flow.title, "title": flow.title,
"layout": "stacked", "layout": "stacked",
}, },
"flow_designation": "authentication",
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"password_fields": False, "password_fields": False,
"primary_action": "Log in", "primary_action": "Log in",

View File

@ -450,7 +450,7 @@ class FlowExecutorView(APIView):
return to_stage_response(self.request, challenge_view.get(self.request)) return to_stage_response(self.request, challenge_view.get(self.request))
def cancel(self): def cancel(self):
"""Cancel current flow execution""" """Cancel current execution and return a redirect"""
keys_to_delete = [ keys_to_delete = [
SESSION_KEY_APPLICATION_PRE, SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN, SESSION_KEY_PLAN,
@ -469,7 +469,7 @@ class FlowExecutorView(APIView):
class CancelView(View): class CancelView(View):
"""View which cancels the currently active plan""" """View which canels the currently active plan"""
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""View which canels the currently active plan""" """View which canels the currently active plan"""

View File

@ -3,9 +3,9 @@
from dataclasses import dataclass from dataclasses import dataclass
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik import __version__, get_build_hash from authentik import __version__, get_build_hash
from authentik.events.logs import LogEvent, capture_logs
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.models import ( from authentik.outposts.models import (
@ -63,21 +63,21 @@ class BaseController:
"""Called by scheduled task to reconcile deployment/service/etc""" """Called by scheduled task to reconcile deployment/service/etc"""
raise NotImplementedError raise NotImplementedError
def up_with_logs(self) -> list[LogEvent]: def up_with_logs(self) -> list[str]:
"""Call .up() but capture all log output and return it.""" """Call .up() but capture all log output and return it."""
with capture_logs() as logs: with capture_logs() as logs:
self.up() self.up()
return logs return [x["event"] for x in logs]
def down(self): def down(self):
"""Handler to delete everything we've created""" """Handler to delete everything we've created"""
raise NotImplementedError raise NotImplementedError
def down_with_logs(self) -> list[LogEvent]: def down_with_logs(self) -> list[str]:
"""Call .down() but capture all log output and return it.""" """Call .down() but capture all log output and return it."""
with capture_logs() as logs: with capture_logs() as logs:
self.down() self.down()
return logs return [x["event"] for x in logs]
def __enter__(self): def __enter__(self):
return self return self

View File

@ -33,8 +33,6 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
# priority than being updated. # priority than being updated.
if current.spec.selector != reference.spec.selector: if current.spec.selector != reference.spec.selector:
raise NeedsUpdate() raise NeedsUpdate()
if current.spec.type != reference.spec.type:
raise NeedsUpdate()
super().reconcile(current, reference) super().reconcile(current, reference)
def get_reference_object(self) -> V1Service: def get_reference_object(self) -> V1Service:

View File

@ -9,10 +9,10 @@ from kubernetes.client.exceptions import OpenApiException
from kubernetes.config.config_exception import ConfigException from kubernetes.config.config_exception import ConfigException
from kubernetes.config.incluster_config import load_incluster_config from kubernetes.config.incluster_config import load_incluster_config
from kubernetes.config.kube_config import load_kube_config_from_dict from kubernetes.config.kube_config import load_kube_config_from_dict
from structlog.testing import capture_logs
from urllib3.exceptions import HTTPError from urllib3.exceptions import HTTPError
from yaml import dump_all from yaml import dump_all
from authentik.events.logs import LogEvent, capture_logs
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
@ -91,7 +91,7 @@ class KubernetesController(BaseController):
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc raise ControllerException(str(exc)) from exc
def up_with_logs(self) -> list[LogEvent]: def up_with_logs(self) -> list[str]:
try: try:
all_logs = [] all_logs = []
for reconcile_key in self.reconcile_order: for reconcile_key in self.reconcile_order:
@ -104,9 +104,7 @@ class KubernetesController(BaseController):
continue continue
reconciler = reconciler_cls(self) reconciler = reconciler_cls(self)
reconciler.up() reconciler.up()
for log in logs: all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
log.logger = reconcile_key.title()
all_logs.extend(logs)
return all_logs return all_logs
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc raise ControllerException(str(exc)) from exc
@ -124,7 +122,7 @@ class KubernetesController(BaseController):
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc raise ControllerException(str(exc)) from exc
def down_with_logs(self) -> list[LogEvent]: def down_with_logs(self) -> list[str]:
try: try:
all_logs = [] all_logs = []
for reconcile_key in self.reconcile_order: for reconcile_key in self.reconcile_order:
@ -137,9 +135,7 @@ class KubernetesController(BaseController):
continue continue
reconciler = reconciler_cls(self) reconciler = reconciler_cls(self)
reconciler.down() reconciler.down()
for log in logs: all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
log.logger = reconcile_key.title()
all_logs.extend(logs)
return all_logs return all_logs
except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc: except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
raise ControllerException(str(exc)) from exc raise ControllerException(str(exc)) from exc

View File

@ -149,8 +149,10 @@ def outpost_controller(
if not controller_type: if not controller_type:
return return
with controller_type(outpost, outpost.service_connection) as controller: with controller_type(outpost, outpost.service_connection) as controller:
LOGGER.debug("---------------Outpost Controller logs starting----------------")
logs = getattr(controller, f"{action}_with_logs")() logs = getattr(controller, f"{action}_with_logs")()
LOGGER.debug("---------------Outpost Controller logs starting----------------")
for log in logs:
LOGGER.debug(log)
LOGGER.debug("-----------------Outpost Controller logs end-------------------") LOGGER.debug("-----------------Outpost Controller logs end-------------------")
except (ControllerException, ServiceConnectionInvalid) as exc: except (ControllerException, ServiceConnectionInvalid) as exc:
self.set_error(exc) self.set_error(exc)

View File

@ -1,11 +1,10 @@
"""Serializer for policy execution""" """Serializer for policy execution"""
from rest_framework.fields import BooleanField, CharField, ListField from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
from authentik.core.api.utils import JSONDictField, PassiveSerializer from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import User from authentik.core.models import User
from authentik.events.logs import LogEventSerializer
class PolicyTestSerializer(PassiveSerializer): class PolicyTestSerializer(PassiveSerializer):
@ -20,4 +19,4 @@ class PolicyTestResultSerializer(PassiveSerializer):
passing = BooleanField() passing = BooleanField()
messages = ListField(child=CharField(), read_only=True) messages = ListField(child=CharField(), read_only=True)
log_messages = LogEventSerializer(many=True, read_only=True) log_messages = ListField(child=DictField(), read_only=True)

View File

@ -11,11 +11,12 @@ from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from structlog.testing import capture_logs
from authentik.core.api.applications import user_app_cache_key from authentik.core.api.applications import user_app_cache_key
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import CacheSerializer, MetaNameSerializer, TypeCreateSerializer from authentik.core.api.utils import CacheSerializer, MetaNameSerializer, TypeCreateSerializer
from authentik.events.logs import LogEventSerializer, capture_logs from authentik.events.utils import sanitize_dict
from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.reflection import all_subclasses
from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer
from authentik.policies.models import Policy, PolicyBinding from authentik.policies.models import Policy, PolicyBinding
@ -165,9 +166,9 @@ class PolicyViewSet(
result = proc.execute() result = proc.execute()
log_messages = [] log_messages = []
for log in logs: for log in logs:
if log.attributes.get("process", "") == "PolicyProcess": if log.get("process", "") == "PolicyProcess":
continue continue
log_messages.append(LogEventSerializer(log).data) log_messages.append(sanitize_dict(log))
result.log_messages = log_messages result.log_messages = log_messages
response = PolicyTestResultSerializer(result) response = PolicyTestResultSerializer(result)
return Response(response.data) return Response(response.data)

View File

@ -13,7 +13,6 @@ from authentik.events.context_processors.base import get_context_processors
if TYPE_CHECKING: if TYPE_CHECKING:
from authentik.core.models import User from authentik.core.models import User
from authentik.events.logs import LogEvent
from authentik.policies.models import PolicyBinding from authentik.policies.models import PolicyBinding
LOGGER = get_logger() LOGGER = get_logger()
@ -75,7 +74,7 @@ class PolicyResult:
source_binding: PolicyBinding | None source_binding: PolicyBinding | None
source_results: list[PolicyResult] | None source_results: list[PolicyResult] | None
log_messages: list[LogEvent] | None log_messages: list[dict] | None
def __init__(self, passing: bool, *messages: str): def __init__(self, passing: bool, *messages: str):
self.passing = passing self.passing = passing

View File

@ -25,7 +25,7 @@ class OAuthDeviceCodeFinishChallengeResponse(ChallengeResponse):
class OAuthDeviceCodeFinishStage(ChallengeStageView): class OAuthDeviceCodeFinishStage(ChallengeStageView):
"""Stage to finish the OAuth device code flow""" """Stage show at the end of a device flow"""
response_class = OAuthDeviceCodeFinishChallengeResponse response_class = OAuthDeviceCodeFinishChallengeResponse

View File

@ -3,7 +3,7 @@
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views import View from django.views import View
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ErrorDetail
from rest_framework.fields import CharField, IntegerField from rest_framework.fields import CharField, IntegerField
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
@ -57,7 +57,6 @@ def validate_code(code: int, request: HttpRequest) -> HttpResponse | None:
scope_descriptions = UserInfoView().get_scope_descriptions(token.scope, token.provider) scope_descriptions = UserInfoView().get_scope_descriptions(token.scope, token.provider)
planner = FlowPlanner(token.provider.authorization_flow) planner = FlowPlanner(token.provider.authorization_flow)
planner.allow_empty_flows = True planner.allow_empty_flows = True
planner.use_cache = False
try: try:
plan = planner.plan( plan = planner.plan(
request, request,
@ -129,13 +128,6 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse):
code = IntegerField() code = IntegerField()
component = CharField(default="ak-provider-oauth2-device-code") component = CharField(default="ak-provider-oauth2-device-code")
def validate_code(self, code: int) -> HttpResponse | None:
"""Validate code and save the returned http response"""
response = validate_code(code, self.stage.request)
if not response:
raise ValidationError("Invalid code", "invalid")
return response
class OAuthDeviceCodeStage(ChallengeStageView): class OAuthDeviceCodeStage(ChallengeStageView):
"""Flow challenge for users to enter device codes""" """Flow challenge for users to enter device codes"""
@ -151,4 +143,12 @@ class OAuthDeviceCodeStage(ChallengeStageView):
) )
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return response.validated_data["code"] code = response.validated_data["code"]
validation = validate_code(code, self.request)
if not validation:
response._errors.setdefault("code", [])
response._errors["code"].append(ErrorDetail(_("Invalid code"), "invalid"))
return self.challenge_invalid(response)
# Run cancel to cleanup the current flow
self.executor.cancel()
return validation

View File

@ -56,10 +56,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
proxy_provider: ProxyProvider proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host) external_host_name = urlparse(proxy_provider.external_host)
expected_hosts.append(external_host_name.hostname) expected_hosts.append(external_host_name.hostname)
if ( if external_host_name.scheme == "https":
external_host_name.scheme == "https"
and self.controller.outpost.config.kubernetes_ingress_secret_name
):
expected_hosts_tls.append(external_host_name.hostname) expected_hosts_tls.append(external_host_name.hostname)
expected_hosts.sort() expected_hosts.sort()
expected_hosts_tls.sort() expected_hosts_tls.sort()
@ -119,10 +116,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
): ):
proxy_provider: ProxyProvider proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host) external_host_name = urlparse(proxy_provider.external_host)
if ( if external_host_name.scheme == "https":
external_host_name.scheme == "https"
and self.controller.outpost.config.kubernetes_ingress_secret_name
):
tls_hosts.append(external_host_name.hostname) tls_hosts.append(external_host_name.hostname)
if proxy_provider.mode in [ if proxy_provider.mode in [
ProxyMode.FORWARD_SINGLE, ProxyMode.FORWARD_SINGLE,
@ -166,15 +160,13 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
rules.append(rule) rules.append(rule)
tls_config = None tls_config = None
if tls_hosts: if tls_hosts:
tls_config = [ tls_config = V1IngressTLS(
V1IngressTLS(
hosts=tls_hosts, hosts=tls_hosts,
secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name, secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name,
) )
]
spec = V1IngressSpec( spec = V1IngressSpec(
rules=rules, rules=rules,
tls=tls_config, tls=[tls_config],
) )
if self.controller.outpost.config.kubernetes_ingress_class_name: if self.controller.outpost.config.kubernetes_ingress_class_name:
spec.ingress_class_name = self.controller.outpost.config.kubernetes_ingress_class_name spec.ingress_class_name = self.controller.outpost.config.kubernetes_ingress_class_name

View File

@ -196,10 +196,8 @@ if CONFIG.get_bool("redis.tls", False):
_redis_protocol_prefix = "rediss://" _redis_protocol_prefix = "rediss://"
_redis_celery_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}" _redis_celery_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}"
_redis_url = ( _redis_url = (
f"{_redis_protocol_prefix}" f"{_redis_protocol_prefix}:"
f"{quote_plus(CONFIG.get('redis.username'))}:" f"{quote_plus(CONFIG.get('redis.password'))}@{quote_plus(CONFIG.get('redis.host'))}:"
f"{quote_plus(CONFIG.get('redis.password'))}@"
f"{quote_plus(CONFIG.get('redis.host'))}:"
f"{CONFIG.get_int('redis.port')}" f"{CONFIG.get_int('redis.port')}"
) )

View File

@ -77,7 +77,6 @@ class LDAPSourceSerializer(SourceSerializer):
"group_object_filter", "group_object_filter",
"group_membership_field", "group_membership_field",
"object_uniqueness_field", "object_uniqueness_field",
"password_login_update_internal_password",
"sync_users", "sync_users",
"sync_users_password", "sync_users_password",
"sync_groups", "sync_groups",
@ -119,7 +118,6 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
"group_object_filter", "group_object_filter",
"group_membership_field", "group_membership_field",
"object_uniqueness_field", "object_uniqueness_field",
"password_login_update_internal_password",
"sync_users", "sync_users",
"sync_users_password", "sync_users_password",
"sync_groups", "sync_groups",

View File

@ -41,7 +41,6 @@ class LDAPBackend(InbuiltBackend):
# or has a password, but couldn't be authenticated by ModelBackend. # or has a password, but couldn't be authenticated by ModelBackend.
# This means we check with a bind to see if the LDAP password has changed # This means we check with a bind to see if the LDAP password has changed
if self.auth_user_by_bind(source, user, password): if self.auth_user_by_bind(source, user, password):
if source.password_login_update_internal_password:
# Password given successfully binds to LDAP, so we save it in our Database # Password given successfully binds to LDAP, so we save it in our Database
LOGGER.debug("Updating user's password in DB", user=user) LOGGER.debug("Updating user's password in DB", user=user)
user.set_password(password, signal=False) user.set_password(password, signal=False)

View File

@ -1,29 +0,0 @@
# Generated by Django 5.0.1 on 2024-01-31 18:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0003_ldapsource_client_certificate_ldapsource_sni_and_more"),
]
operations = [
migrations.AddField(
model_name="ldapsource",
name="password_login_update_internal_password",
field=models.BooleanField(
default=True,
help_text="Update internal authentik password when login succeeds with LDAP",
),
),
migrations.AlterField(
model_name="ldapsource",
name="password_login_update_internal_password",
field=models.BooleanField(
default=False,
help_text="Update internal authentik password when login succeeds with LDAP",
),
),
]

View File

@ -98,11 +98,6 @@ class LDAPSource(Source):
help_text=_("Property mappings used for group creation/updating."), help_text=_("Property mappings used for group creation/updating."),
) )
password_login_update_internal_password = models.BooleanField(
default=False,
help_text=_("Update internal authentik password when login succeeds with LDAP"),
)
sync_users = models.BooleanField(default=True) sync_users = models.BooleanField(default=True)
sync_users_password = models.BooleanField( sync_users_password = models.BooleanField(
default=True, default=True,

View File

@ -47,7 +47,7 @@ class SourceType:
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge: def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
"""Allow types to return custom challenges""" """Allow types to return custom challenges"""
return RedirectChallenge( return RedirectChallenge(
data={ instance={
"type": ChallengeTypes.REDIRECT.value, "type": ChallengeTypes.REDIRECT.value,
"to": reverse( "to": reverse(
"authentik_sources_oauth:oauth-client-login", "authentik_sources_oauth:oauth-client-login",

View File

@ -54,7 +54,7 @@ class OAuthCallback(OAuthClientMixin, View):
raw_profile=exc.doc, raw_profile=exc.doc,
).from_http(self.request) ).from_http(self.request)
return self.handle_login_failure("Could not retrieve profile.") return self.handle_login_failure("Could not retrieve profile.")
identifier = self.get_user_id(info=raw_info) identifier = self.get_user_id(raw_info)
if identifier is None: if identifier is None:
return self.handle_login_failure("Could not determine id.") return self.handle_login_failure("Could not determine id.")
# Get or create access record # Get or create access record
@ -67,7 +67,6 @@ class OAuthCallback(OAuthClientMixin, View):
) )
sfm.policy_context = {"oauth_userinfo": raw_info} sfm.policy_context = {"oauth_userinfo": raw_info}
return sfm.get_flow( return sfm.get_flow(
raw_info=raw_info,
access_token=self.token.get("access_token"), access_token=self.token.get("access_token"),
) )
@ -117,7 +116,6 @@ class OAuthSourceFlowManager(SourceFlowManager):
self, self,
connection: UserOAuthSourceConnection, connection: UserOAuthSourceConnection,
access_token: str | None = None, access_token: str | None = None,
**_,
) -> UserOAuthSourceConnection: ) -> UserOAuthSourceConnection:
"""Set the access_token on the connection""" """Set the access_token on the connection"""
connection.access_token = access_token connection.access_token = access_token

View File

@ -190,7 +190,7 @@ class SAMLSource(Source):
def ui_login_button(self, request: HttpRequest) -> UILoginButton: def ui_login_button(self, request: HttpRequest) -> UILoginButton:
return UILoginButton( return UILoginButton(
challenge=RedirectChallenge( challenge=RedirectChallenge(
data={ instance={
"type": ChallengeTypes.REDIRECT.value, "type": ChallengeTypes.REDIRECT.value,
"to": reverse( "to": reverse(
"authentik_sources_saml:login", "authentik_sources_saml:login",

View File

@ -234,14 +234,12 @@ class ResponseProcessor:
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT: if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
return self._handle_name_id_transient() return self._handle_name_id_transient()
flow_manager = SAMLSourceFlowManager( return SAMLSourceFlowManager(
self._source, self._source,
self._http_request, self._http_request,
name_id.text, name_id.text,
delete_none_values(self.get_attributes()), delete_none_values(self.get_attributes()),
) )
flow_manager.policy_context["saml_response"] = self._root
return flow_manager
class SAMLSourceFlowManager(SourceFlowManager): class SAMLSourceFlowManager(SourceFlowManager):

View File

@ -120,9 +120,7 @@ def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Dev
stage=stage_view.executor.current_stage, stage=stage_view.executor.current_stage,
device_class=DeviceClasses.TOTP.value, device_class=DeviceClasses.TOTP.value,
) )
raise ValidationError( raise ValidationError(_("Invalid Token"))
_("Invalid Token. Please ensure the time on your device is accurate and try again.")
)
return device return device

View File

@ -12,7 +12,6 @@ class DummyChallenge(Challenge):
"""Dummy challenge""" """Dummy challenge"""
component = CharField(default="ak-stage-dummy") component = CharField(default="ak-stage-dummy")
name = CharField()
class DummyChallengeResponse(ChallengeResponse): class DummyChallengeResponse(ChallengeResponse):
@ -36,6 +35,5 @@ class DummyStageView(ChallengeStageView):
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"title": self.executor.current_stage.name, "title": self.executor.current_stage.name,
"name": self.executor.current_stage.name,
} }
) )

View File

@ -10,7 +10,7 @@ from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, ListField from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from sentry_sdk.hub import Hub from sentry_sdk.hub import Hub
@ -66,7 +66,6 @@ class IdentificationChallenge(Challenge):
user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True) user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
password_fields = BooleanField() password_fields = BooleanField()
application_pre = CharField(required=False) application_pre = CharField(required=False)
flow_designation = ChoiceField(FlowDesignation.choices)
enroll_url = CharField(required=False) enroll_url = CharField(required=False)
recovery_url = CharField(required=False) recovery_url = CharField(required=False)
@ -195,12 +194,11 @@ class IdentificationStageView(ChallengeStageView):
challenge = IdentificationChallenge( challenge = IdentificationChallenge(
data={ data={
"type": ChallengeTypes.NATIVE.value, "type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"primary_action": self.get_primary_action(), "primary_action": self.get_primary_action(),
"component": "ak-stage-identification",
"user_fields": current_stage.user_fields, "user_fields": current_stage.user_fields,
"password_fields": bool(current_stage.password_stage), "password_fields": bool(current_stage.password_stage),
"show_source_labels": current_stage.show_source_labels, "show_source_labels": current_stage.show_source_labels,
"flow_designation": self.executor.flow.designation,
} }
) )
# If the user has been redirected to us whilst trying to access an # If the user has been redirected to us whilst trying to access an
@ -239,9 +237,7 @@ class IdentificationStageView(ChallengeStageView):
ui_login_button = source.ui_login_button(self.request) ui_login_button = source.ui_login_button(self.request)
if ui_login_button: if ui_login_button:
button = asdict(ui_login_button) button = asdict(ui_login_button)
source_challenge = ui_login_button.challenge button["challenge"] = ui_login_button.challenge.data
source_challenge.is_valid()
button["challenge"] = source_challenge.data
ui_sources.append(button) ui_sources.append(button)
challenge.initial_data["sources"] = ui_sources challenge.initial_data["sources"] = ui_sources
return challenge return challenge

View File

@ -12,7 +12,6 @@ from rest_framework.exceptions import ValidationError
from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER
from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.events.utils import sanitize_item
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView from authentik.flows.stage import StageView
from authentik.flows.views.executor import FlowExecutorView from authentik.flows.views.executor import FlowExecutorView
@ -48,7 +47,7 @@ class UserWriteStageView(StageView):
# this is just a sanity check to ensure that is removed # this is just a sanity check to ensure that is removed
if parts[0] == "attributes": if parts[0] == "attributes":
parts = parts[1:] parts = parts[1:]
set_path_in_dict(user.attributes, ".".join(parts), sanitize_item(value)) set_path_in_dict(user.attributes, ".".join(parts), value)
def ensure_user(self) -> tuple[User | None, bool]: def ensure_user(self) -> tuple[User | None, bool]:
"""Ensure a user exists""" """Ensure a user exists"""

View File

@ -2594,43 +2594,6 @@
} }
} }
}, },
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_stages_source.sourcestage"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created",
"must_created"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"attrs": {
"$ref": "#/$defs/model_authentik_stages_source.sourcestage"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_stages_source.sourcestage"
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -3294,7 +3257,6 @@
"authentik.enterprise", "authentik.enterprise",
"authentik.enterprise.audit", "authentik.enterprise.audit",
"authentik.enterprise.providers.rac", "authentik.enterprise.providers.rac",
"authentik.enterprise.stages.source",
"authentik.events" "authentik.events"
], ],
"title": "App", "title": "App",
@ -3376,7 +3338,6 @@
"authentik_providers_rac.racprovider", "authentik_providers_rac.racprovider",
"authentik_providers_rac.endpoint", "authentik_providers_rac.endpoint",
"authentik_providers_rac.racpropertymapping", "authentik_providers_rac.racpropertymapping",
"authentik_stages_source.sourcestage",
"authentik_events.event", "authentik_events.event",
"authentik_events.notificationtransport", "authentik_events.notificationtransport",
"authentik_events.notification", "authentik_events.notification",
@ -4347,11 +4308,6 @@
"title": "Object uniqueness field", "title": "Object uniqueness field",
"description": "Field which contains a unique Identifier." "description": "Field which contains a unique Identifier."
}, },
"password_login_update_internal_password": {
"type": "boolean",
"title": "Password login update internal password",
"description": "Update internal authentik password when login succeeds with LDAP"
},
"sync_users": { "sync_users": {
"type": "boolean", "type": "boolean",
"title": "Sync users" "title": "Sync users"
@ -8062,109 +8018,6 @@
}, },
"required": [] "required": []
}, },
"model_authentik_stages_source.sourcestage": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"flow_set": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"slug": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "^[-a-zA-Z0-9_]+$",
"title": "Slug",
"description": "Visible in the URL."
},
"title": {
"type": "string",
"minLength": 1,
"title": "Title",
"description": "Shown as the Title in Flow pages."
},
"designation": {
"type": "string",
"enum": [
"authentication",
"authorization",
"invalidation",
"enrollment",
"unenrollment",
"recovery",
"stage_configuration"
],
"title": "Designation",
"description": "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
},
"policy_engine_mode": {
"type": "string",
"enum": [
"all",
"any"
],
"title": "Policy engine mode"
},
"compatibility_mode": {
"type": "boolean",
"title": "Compatibility mode",
"description": "Enable compatibility mode, increases compatibility with password managers on mobile devices."
},
"layout": {
"type": "string",
"enum": [
"stacked",
"content_left",
"content_right",
"sidebar_left",
"sidebar_right"
],
"title": "Layout"
},
"denied_action": {
"type": "string",
"enum": [
"message_continue",
"message",
"continue"
],
"title": "Denied action",
"description": "Configure what should happen when a flow denies access to a user."
}
},
"required": [
"name",
"slug",
"title",
"designation"
]
},
"title": "Flow set"
},
"source": {
"type": "integer",
"title": "Source"
},
"resume_timeout": {
"type": "string",
"minLength": 1,
"title": "Resume timeout",
"description": "Amount of time a user can take to return from the source to continue the flow (Format: hours=-1;minutes=-2;seconds=-3)"
}
},
"required": []
},
"model_authentik_events.event": { "model_authentik_events.event": {
"type": "object", "type": "object",
"properties": { "properties": {

2
go.mod
View File

@ -30,7 +30,7 @@ require (
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2 github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024022.7 goauthentik.io/api/v3 v3.2024022.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.18.0 golang.org/x/oauth2 v0.18.0
golang.org/x/sync v0.6.0 golang.org/x/sync v0.6.0

4
go.sum
View File

@ -280,8 +280,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2024022.7 h1:VR9OmcZvTzPSjit2Dx2EoHrLc9v9XRyjPXNpnGISWWM= goauthentik.io/api/v3 v3.2024022.1 h1:ydYi3X/OSnu4LumUN+oCe6vvGDXil1Xn186hC9FQb4Q=
goauthentik.io/api/v3 v3.2024022.7/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= goauthentik.io/api/v3 v3.2024022.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -10,17 +10,12 @@ const CodePasswordSeparator = ";"
var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`) var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`)
// Sets the secret answers for the flow executor for protocols that only support username/password // CheckPasswordInlineMFA For protocols that only support username/password, check if the password
// according to used options // contains the TOTP code
func (fe *FlowExecutor) SetSecrets(password string, mfaCodeBased bool) { func (fe *FlowExecutor) CheckPasswordInlineMFA() {
if fe.Answers[StageAuthenticatorValidate] != "" || fe.Answers[StagePassword] != "" { password := fe.Answers[StagePassword]
return // We already have an authenticator answer
} if fe.Answers[StageAuthenticatorValidate] != "" {
fe.Answers[StagePassword] = password
if !mfaCodeBased {
// If code-based MFA is disabled StageAuthenticatorValidate answer is set to password.
// This allows flows with a mfa stage only.
fe.Answers[StageAuthenticatorValidate] = password
return return
} }
// password doesn't contain the separator // password doesn't contain the separator

View File

@ -23,7 +23,10 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
fe.Params.Add("goauthentik.io/outpost/ldap", "true") fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Answers[flow.StageIdentification] = username fe.Answers[flow.StageIdentification] = username
fe.SetSecrets(req.BindPW, db.si.GetMFASupport()) fe.Answers[flow.StagePassword] = req.BindPW
if db.si.GetMFASupport() {
fe.CheckPasswordInlineMFA()
}
passed, err := fe.Execute() passed, err := fe.Execute()
flags := flags.UserFlags{ flags := flags.UserFlags{

View File

@ -35,7 +35,7 @@ type ProviderInstance struct {
cert *tls.Certificate cert *tls.Certificate
certUUID string certUUID string
outpostName string outpostName string
providerPk int32 outpostPk int32
searchAllowedGroups []*strfmt.UUID searchAllowedGroups []*strfmt.UUID
boundUsersMutex *sync.RWMutex boundUsersMutex *sync.RWMutex
boundUsers map[string]*flags.UserFlags boundUsers map[string]*flags.UserFlags

View File

@ -22,7 +22,7 @@ import (
func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance { func (ls *LDAPServer) getCurrentProvider(pk int32) *ProviderInstance {
for _, p := range ls.providers { for _, p := range ls.providers {
if p.providerPk == pk { if p.outpostPk == pk {
return p return p
} }
} }
@ -83,7 +83,7 @@ func (ls *LDAPServer) Refresh() error {
gidStartNumber: provider.GetGidStartNumber(), gidStartNumber: provider.GetGidStartNumber(),
mfaSupport: provider.GetMfaSupport(), mfaSupport: provider.GetMfaSupport(),
outpostName: ls.ac.Outpost.Name, outpostName: ls.ac.Outpost.Name,
providerPk: provider.Pk, outpostPk: provider.Pk,
} }
if kp := provider.Certificate.Get(); kp != nil { if kp := provider.Certificate.Get(); kp != nil {
err := ls.cs.AddKeypair(*kp) err := ls.cs.AddKeypair(*kp)

View File

@ -56,7 +56,7 @@ func TestProxy_Redirect_Subdirectory(t *testing.T) {
loc, _ := rr.Result().Location() loc, _ := rr.Result().Location()
assert.Equal( assert.Equal(
t, t,
"https://ext.t.goauthentik.io/subdir/outpost.goauthentik.io/start?rd=https%3A%2F%2Fext.t.goauthentik.io%2Fsubdir%2Ffoo", "https://ext.t.goauthentik.io/subdir/outpost.goauthentik.io/start?rd=https%3A%2F%2Fext.t.goauthentik.io%2Ffoo",
loc.String(), loc.String(),
) )
} }

View File

@ -33,7 +33,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
if a.isEmbedded { if a.isEmbedded {
client := redis.NewClient(&redis.Options{ client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
Username: config.Get().Redis.Username, // Username: config.Get().Redis.Password,
Password: config.Get().Redis.Password, Password: config.Get().Redis.Password,
DB: config.Get().Redis.DB, DB: config.Get().Redis.DB,
}) })

View File

@ -3,6 +3,7 @@ package application
import ( import (
"net/http" "net/http"
"net/url" "net/url"
"path"
"strconv" "strconv"
"strings" "strings"
@ -10,12 +11,22 @@ import (
"goauthentik.io/internal/outpost/proxyv2/constants" "goauthentik.io/internal/outpost/proxyv2/constants"
) )
func urlJoin(originalUrl string, newPath string) string { func urlPathSet(originalUrl string, newPath string) string {
u, err := url.JoinPath(originalUrl, newPath) u, err := url.Parse(originalUrl)
if err != nil { if err != nil {
return originalUrl return originalUrl
} }
return u u.Path = newPath
return u.String()
}
func urlJoin(originalUrl string, newPath string) string {
u, err := url.Parse(originalUrl)
if err != nil {
return originalUrl
}
u.Path = path.Join(u.Path, newPath)
return u.String()
} }
func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) { func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
@ -35,7 +46,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
} }
} }
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path) redirectUrl := urlPathSet(a.proxyConfig.ExternalHost, r.URL.Path)
if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN { if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".") dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")

View File

@ -6,10 +6,8 @@ import (
"net" "net"
"sort" "sort"
"strings" "strings"
"sync"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/ldap/flags"
) )
func parseCIDRs(raw string) []*net.IPNet { func parseCIDRs(raw string) []*net.IPNet {
@ -31,25 +29,6 @@ func parseCIDRs(raw string) []*net.IPNet {
return cidrs return cidrs
} }
func (rs *RadiusServer) getCurrentProvider(pk int32) *ProviderInstance {
for _, p := range rs.providers {
if p.providerPk == pk {
return p
}
}
return nil
}
func (rs *RadiusServer) getInvalidationFlow() string {
req, _, err := rs.ac.Client.CoreApi.CoreBrandsCurrentRetrieve(context.Background()).Execute()
if err != nil {
rs.log.WithError(err).Warning("failed to fetch brand config")
return ""
}
flow := req.GetFlowInvalidation()
return flow
}
func (rs *RadiusServer) Refresh() error { func (rs *RadiusServer) Refresh() error {
outposts, _, err := rs.ac.Client.OutpostsApi.OutpostsRadiusList(context.Background()).Execute() outposts, _, err := rs.ac.Client.OutpostsApi.OutpostsRadiusList(context.Background()).Execute()
if err != nil { if err != nil {
@ -58,33 +37,17 @@ func (rs *RadiusServer) Refresh() error {
if len(outposts.Results) < 1 { if len(outposts.Results) < 1 {
return errors.New("no radius provider defined") return errors.New("no radius provider defined")
} }
invalidationFlow := rs.getInvalidationFlow()
providers := make([]*ProviderInstance, len(outposts.Results)) providers := make([]*ProviderInstance, len(outposts.Results))
for idx, provider := range outposts.Results { for idx, provider := range outposts.Results {
logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name) logger := log.WithField("logger", "authentik.outpost.radius").WithField("provider", provider.Name)
// Get existing instance so we can transfer boundUsers
existing := rs.getCurrentProvider(provider.Pk)
usersMutex := &sync.RWMutex{}
users := make(map[string]*flags.UserFlags)
if existing != nil {
usersMutex = existing.boundUsersMutex
// Shallow copy, no need to lock
users = existing.boundUsers
}
providers[idx] = &ProviderInstance{ providers[idx] = &ProviderInstance{
SharedSecret: []byte(provider.GetSharedSecret()), SharedSecret: []byte(provider.GetSharedSecret()),
ClientNetworks: parseCIDRs(provider.GetClientNetworks()), ClientNetworks: parseCIDRs(provider.GetClientNetworks()),
MFASupport: provider.GetMfaSupport(), MFASupport: provider.GetMfaSupport(),
appSlug: provider.ApplicationSlug, appSlug: provider.ApplicationSlug,
authenticationFlowSlug: provider.AuthFlowSlug, flowSlug: provider.AuthFlowSlug,
invalidationFlowSlug: invalidationFlow,
s: rs, s: rs,
log: logger, log: logger,
providerPk: provider.Pk,
boundUsersMutex: usersMutex,
boundUsers: users,
} }
} }
rs.providers = providers rs.providers = providers

View File

@ -4,17 +4,15 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow" "goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/radius/metrics" "goauthentik.io/internal/outpost/radius/metrics"
"layeh.com/radius" "layeh.com/radius"
"layeh.com/radius/rfc2865" "layeh.com/radius/rfc2865"
"layeh.com/radius/rfc2866"
) )
func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) { func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusRequest) {
username := rfc2865.UserName_GetString(r.Packet) username := rfc2865.UserName_GetString(r.Packet)
fe := flow.NewFlowExecutor(r.Context(), r.pi.authenticationFlowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{ fe := flow.NewFlowExecutor(r.Context(), r.pi.flowSlug, r.pi.s.ac.Client.GetConfig(), log.Fields{
"username": username, "username": username,
"client": r.RemoteAddr(), "client": r.RemoteAddr(),
"requestId": r.ID(), "requestId": r.ID(),
@ -23,7 +21,10 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
fe.Params.Add("goauthentik.io/outpost/radius", "true") fe.Params.Add("goauthentik.io/outpost/radius", "true")
fe.Answers[flow.StageIdentification] = username fe.Answers[flow.StageIdentification] = username
fe.SetSecrets(rfc2865.UserPassword_GetString(r.Packet), r.pi.MFASupport) fe.Answers[flow.StagePassword] = rfc2865.UserPassword_GetString(r.Packet)
if r.pi.MFASupport {
fe.CheckPasswordInlineMFA()
}
passed, err := fe.Execute() passed, err := fe.Execute()
if err != nil { if err != nil {
@ -66,28 +67,5 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
}).Inc() }).Inc()
return return
} }
// Get user info to store in context _ = w.Write(r.Response(radius.CodeAccessAccept))
userInfo, _, err := fe.ApiClient().CoreApi.CoreUsersMeRetrieve(r.Context()).Execute()
if err != nil {
metrics.RequestsRejected.With(prometheus.Labels{
"outpost_name": rs.ac.Outpost.Name,
"type": "bind",
"reason": "user_info_fail",
}).Inc()
r.Log().WithError(err).Warning("failed to get user info")
return
}
response := r.Response(radius.CodeAccessAccept)
_ = rfc2866.AcctSessionID_SetString(response, fe.GetSession().String())
r.pi.boundUsersMutex.Lock()
r.pi.boundUsers[fe.GetSession().String()] = &flags.UserFlags{
Session: fe.GetSession(),
UserPk: userInfo.Original.Pk,
}
r.pi.boundUsersMutex.Unlock()
err = w.Write(response)
if err != nil {
r.Log().WithError(err).Warning("failed to write response")
}
} }

View File

@ -1,54 +0,0 @@
package radius
import (
log "github.com/sirupsen/logrus"
"goauthentik.io/internal/outpost/flow"
"goauthentik.io/internal/outpost/ldap/flags"
"layeh.com/radius"
"layeh.com/radius/rfc2866"
)
func (rs *RadiusServer) Handle_DisconnectRequest(w radius.ResponseWriter, r *RadiusRequest) {
session := rfc2866.AcctSessionID_GetString(r.Packet)
sendFailResponse := func() {
failResponse := r.Response(radius.CodeDisconnectACK)
err := w.Write(failResponse)
if err != nil {
r.Log().WithError(err).Warning("failed to write response")
}
}
r.pi.boundUsersMutex.Lock()
var f *flags.UserFlags
if ff, ok := r.pi.boundUsers[session]; !ok {
r.pi.boundUsersMutex.Unlock()
sendFailResponse()
return
} else {
f = ff
}
r.pi.boundUsersMutex.Unlock()
fe := flow.NewFlowExecutor(r.Context(), r.pi.invalidationFlowSlug, rs.ac.Client.GetConfig(), log.Fields{
"client": r.RemoteAddr(),
"requestId": r.ID(),
})
fe.SetSession(f.Session)
fe.DelegateClientIP(r.RemoteAddr())
fe.Params.Add("goauthentik.io/outpost/radius", "true")
_, err := fe.Execute()
if err != nil {
r.log.WithError(err).Warning("failed to logout user")
sendFailResponse()
return
}
r.pi.boundUsersMutex.Lock()
delete(r.pi.boundUsers, session)
r.pi.boundUsersMutex.Unlock()
response := r.Response(radius.CodeDisconnectACK)
err = w.Write(response)
if err != nil {
r.Log().WithError(err).Warning("failed to write response")
}
}

View File

@ -74,12 +74,7 @@ func (rs *RadiusServer) ServeRADIUS(w radius.ResponseWriter, r *radius.Request)
} }
nr.pi = pi nr.pi = pi
switch nr.Code { if nr.Code == radius.CodeAccessRequest {
case radius.CodeAccessRequest:
rs.Handle_AccessRequest(w, nr) rs.Handle_AccessRequest(w, nr)
case radius.CodeDisconnectRequest:
rs.Handle_DisconnectRequest(w, nr)
default:
nr.Log().WithField("code", nr.Code.String()).Debug("Unsupported packet code")
} }
} }

View File

@ -9,7 +9,6 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/internal/config" "goauthentik.io/internal/config"
"goauthentik.io/internal/outpost/ak" "goauthentik.io/internal/outpost/ak"
"goauthentik.io/internal/outpost/ldap/flags"
"goauthentik.io/internal/outpost/radius/metrics" "goauthentik.io/internal/outpost/radius/metrics"
"layeh.com/radius" "layeh.com/radius"
@ -19,13 +18,9 @@ type ProviderInstance struct {
ClientNetworks []*net.IPNet ClientNetworks []*net.IPNet
SharedSecret []byte SharedSecret []byte
MFASupport bool MFASupport bool
boundUsersMutex *sync.RWMutex
boundUsers map[string]*flags.UserFlags
providerPk int32
appSlug string appSlug string
authenticationFlowSlug string flowSlug string
invalidationFlowSlug string
s *RadiusServer s *RadiusServer
log *log.Entry log *log.Entry
} }

View File

@ -14,27 +14,26 @@ import (
) )
func (ws *WebServer) configureStatic() { func (ws *WebServer) configureStatic() {
staticRouter := ws.lh.NewRoute().Subrouter() statRouter := ws.lh.NewRoute().Subrouter()
staticRouter.Use(ws.staticHeaderMiddleware) statRouter.Use(ws.staticHeaderMiddleware)
staticRouter.Use(web.DisableIndex) indexLessRouter := statRouter.NewRoute().Subrouter()
indexLessRouter.Use(web.DisableIndex)
distFs := http.FileServer(http.Dir("./web/dist")) distFs := http.FileServer(http.Dir("./web/dist"))
distHandler := http.StripPrefix("/static/dist/", distFs)
authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik"))) authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik")))
helpHandler := http.FileServer(http.Dir("./website/help/"))
indexLessRouter.PathPrefix("/static/dist/").Handler(distHandler)
indexLessRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
// Root file paths, from which they should be accessed // Prevent font-loading issues on safari, which loads fonts relatively to the URL the browser is on
staticRouter.PathPrefix("/static/dist/").Handler(http.StripPrefix("/static/dist/", distFs)) indexLessRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
staticRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
// Also serve assets folder in specific interfaces since fonts in patternfly are imported
// with a relative path
staticRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r) web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r)
}) })
staticRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs)) indexLessRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
staticRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs)) indexLessRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs))
staticRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { indexLessRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r) web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r)
@ -43,13 +42,12 @@ func (ws *WebServer) configureStatic() {
// Media files, if backend is file // Media files, if backend is file
if config.Get().Storage.Media.Backend == "file" { if config.Get().Storage.Media.Backend == "file" {
fsMedia := http.FileServer(http.Dir(config.Get().Storage.Media.File.Path)) fsMedia := http.FileServer(http.Dir(config.Get().Storage.Media.File.Path))
staticRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fsMedia)) indexLessRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fsMedia))
} }
staticRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", http.FileServer(http.Dir("./website/help/")))) statRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", helpHandler))
staticRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently)) statRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently))
// Static misc files
ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header()["Content-Type"] = []string{"text/plain"} rw.Header()["Content-Type"] = []string{"text/plain"}
rw.WriteHeader(200) rw.WriteHeader(200)

View File

@ -64,7 +64,6 @@ def release_lock(cursor: Cursor):
"""Release database lock""" """Release database lock"""
if not LOCKED: if not LOCKED:
return return
LOGGER.info("releasing database lock")
cursor.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,)) cursor.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,))

View File

@ -1,12 +0,0 @@
from lifecycle.migrate import BaseMigration
class Migration(BaseMigration):
def needs_migration(self) -> bool:
self.cur.execute(
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'template';"
)
return not bool(self.cur.rowcount)
def run(self):
self.cur.execute("CREATE SCHEMA IF NOT EXISTS template; COMMIT;")

View File

@ -116,7 +116,6 @@ class Migration(BaseMigration):
host=CONFIG.get("redis.host"), host=CONFIG.get("redis.host"),
port=6379, port=6379,
db=db, db=db,
username=CONFIG.get("redis.username"),
password=CONFIG.get("redis.password"), password=CONFIG.get("redis.password"),
) )
redis.flushall() redis.flushall()

View File

@ -19,7 +19,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-05 00:07+0000\n" "POT-Creation-Date: 2024-03-01 00:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n" "Last-Translator: Marc Schmitt, 2024\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -802,10 +802,6 @@ msgstr "Jeton du flux"
msgid "Flow Tokens" msgid "Flow Tokens"
msgstr "Jetons du flux" msgstr "Jetons du flux"
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL suivante invalide"
#: authentik/lib/utils/time.py #: authentik/lib/utils/time.py
#, python-format #, python-format
msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'." msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'."

305
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
@ -392,33 +392,33 @@ files = [
[[package]] [[package]]
name = "black" name = "black"
version = "24.3.0" version = "24.2.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, {file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"},
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, {file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"},
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, {file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"},
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, {file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"},
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, {file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"},
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, {file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"},
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, {file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"},
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, {file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"},
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, {file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"},
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, {file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"},
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, {file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"},
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, {file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"},
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, {file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"},
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, {file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"},
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, {file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"},
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, {file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"},
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, {file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"},
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, {file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"},
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, {file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"},
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, {file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"},
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, {file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"},
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, {file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"},
] ]
[package.dependencies] [package.dependencies]
@ -455,19 +455,19 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]] [[package]]
name = "botocore" name = "botocore"
version = "1.34.63" version = "1.34.15"
description = "Low-level, data-driven core of boto 3." description = "Low-level, data-driven core of boto 3."
optional = false optional = false
python-versions = ">= 3.8" python-versions = ">= 3.8"
files = [ files = [
{file = "botocore-1.34.63-py3-none-any.whl", hash = "sha256:8a6cbc3a5c5988725c00815f8f7f6baf81980b19d9a2ee414b031e726759dba9"}, {file = "botocore-1.34.15-py3-none-any.whl", hash = "sha256:16bcf871e67ef0177593f06e9e5bae4db51c9a9a2e953cb14feeb42d53441a85"},
{file = "botocore-1.34.63.tar.gz", hash = "sha256:2237743fc3ed68319bc358b451e7c13a02110242b1522b839806fd64fcee45fb"}, {file = "botocore-1.34.15.tar.gz", hash = "sha256:c3c3404962a6d9d5e1634bd70ed53b8eff1ff17ee9d7a6240e9e8c94db48ad6f"},
] ]
[package.dependencies] [package.dependencies]
jmespath = ">=0.7.1,<2.0.0" jmespath = ">=0.7.1,<2.0.0"
python-dateutil = ">=2.1,<3.0.0" python-dateutil = ">=2.1,<3.0.0"
urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}
[package.extras] [package.extras]
crt = ["awscrt (==0.19.19)"] crt = ["awscrt (==0.19.19)"]
@ -919,63 +919,63 @@ files = [
[[package]] [[package]]
name = "coverage" name = "coverage"
version = "7.4.4" version = "7.4.3"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"},
{file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"},
{file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"},
{file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"},
{file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"},
{file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"},
{file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"},
{file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"},
{file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"},
{file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"},
{file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"},
{file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"},
{file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"},
{file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"},
{file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"},
{file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"},
{file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"},
{file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"},
{file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"},
{file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"},
{file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"},
{file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"},
] ]
[package.extras] [package.extras]
@ -1142,13 +1142,13 @@ bcrypt = ["bcrypt"]
[[package]] [[package]]
name = "django-filter" name = "django-filter"
version = "24.2" version = "24.1"
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"}, {file = "django-filter-24.1.tar.gz", hash = "sha256:65cb43ce272077e5ac6aae1054d76c121cd6b552e296a82a13921e9371baf8c1"},
{file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"}, {file = "django_filter-24.1-py3-none-any.whl", hash = "sha256:335bcae6cbd3e984b024841070f567b22faea57594f27d37c52f8f131f8d8621"},
] ]
[package.dependencies] [package.dependencies]
@ -1389,13 +1389,13 @@ files = [
[[package]] [[package]]
name = "duo-client" name = "duo-client"
version = "5.3.0" version = "5.2.0"
description = "Reference client for Duo Security APIs" description = "Reference client for Duo Security APIs"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "duo_client-5.3.0-py3-none-any.whl", hash = "sha256:85614bb684cef96285268aef0c1e858df939f6e8a190fb2c707d700bb0215766"}, {file = "duo_client-5.2.0-py3-none-any.whl", hash = "sha256:da3237e34300665c40ba5215f1e6656fec1a0136295917541aa973e7fcbf027e"},
{file = "duo_client-5.3.0.tar.gz", hash = "sha256:afa5ef98a42f06965a2702ca41dba9c85c483abd945e0a440f0ec4871b7593bf"}, {file = "duo_client-5.2.0.tar.gz", hash = "sha256:f82361740792b06303f9721e7ba593916080461769396b4f73c0502c0bfcee44"},
] ]
[package.dependencies] [package.dependencies]
@ -1712,13 +1712,13 @@ files = [
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "7.1.0" version = "7.0.2"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"},
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"},
] ]
[package.dependencies] [package.dependencies]
@ -1727,7 +1727,7 @@ zipp = ">=0.5"
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"] perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]] [[package]]
name = "incremental" name = "incremental"
@ -3332,30 +3332,32 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]] [[package]]
name = "requests-mock" name = "requests-mock"
version = "1.12.1" version = "1.11.0"
description = "Mock out responses from the requests package" description = "Mock out responses from the requests package"
optional = false optional = false
python-versions = ">=3.5" python-versions = "*"
files = [ files = [
{file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"},
{file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"},
] ]
[package.dependencies] [package.dependencies]
requests = ">=2.22,<3" requests = ">=2.3,<3"
six = "*"
[package.extras] [package.extras]
fixture = ["fixtures"] fixture = ["fixtures"]
test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"]
[[package]] [[package]]
name = "requests-oauthlib" name = "requests-oauthlib"
version = "2.0.0" version = "1.4.0"
description = "OAuthlib authentication support for Requests." description = "OAuthlib authentication support for Requests."
optional = false optional = false
python-versions = ">=3.4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [ files = [
{file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests-oauthlib-1.4.0.tar.gz", hash = "sha256:acee623221e4a39abcbb919312c8ff04bd44e7e417087fb4bd5e2a2f53d5e79a"},
{file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, {file = "requests_oauthlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:7a3130d94a17520169e38db6c8d75f2c974643788465ecc2e4b36d288bf13033"},
] ]
[package.dependencies] [package.dependencies]
@ -3507,28 +3509,28 @@ pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.3.4" version = "0.3.2"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"},
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"},
{file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, {file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"},
{file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, {file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"},
{file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, {file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"},
{file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, {file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"},
] ]
[[package]] [[package]]
@ -3550,13 +3552,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
[[package]] [[package]]
name = "selenium" name = "selenium"
version = "4.19.0" version = "4.18.1"
description = "" description = ""
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"}, {file = "selenium-4.18.1-py3-none-any.whl", hash = "sha256:b24a3cdd2d47c29832e81345bfcde0c12bb608738013e53c781b211b418df241"},
{file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"}, {file = "selenium-4.18.1.tar.gz", hash = "sha256:a11f67afa8bfac6b77e148c987b33f6b14eb1cae4d352722a75de1f26e3f0ae2"},
] ]
[package.dependencies] [package.dependencies]
@ -3568,13 +3570,13 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
[[package]] [[package]]
name = "sentry-sdk" name = "sentry-sdk"
version = "1.44.0" version = "1.41.0"
description = "Python client for Sentry (https://sentry.io)" description = "Python client for Sentry (https://sentry.io)"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "sentry-sdk-1.44.0.tar.gz", hash = "sha256:f7125a9235795811962d52ff796dc032cd1d0dd98b59beaced8380371cd9c13c"}, {file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"},
{file = "sentry_sdk-1.44.0-py2.py3-none-any.whl", hash = "sha256:eb65289da013ca92fad2694851ad2f086aa3825e808dc285bd7dcaf63602bb18"}, {file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"},
] ]
[package.dependencies] [package.dependencies]
@ -3588,7 +3590,6 @@ asyncpg = ["asyncpg (>=0.23)"]
beam = ["apache-beam (>=2.12)"] beam = ["apache-beam (>=2.12)"]
bottle = ["bottle (>=0.12.13)"] bottle = ["bottle (>=0.12.13)"]
celery = ["celery (>=3)"] celery = ["celery (>=3)"]
celery-redbeat = ["celery-redbeat (>=2)"]
chalice = ["chalice (>=1.16.0)"] chalice = ["chalice (>=1.16.0)"]
clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] clickhouse-driver = ["clickhouse-driver (>=0.2.0)"]
django = ["django (>=1.8)"] django = ["django (>=1.8)"]
@ -3599,7 +3600,6 @@ grpcio = ["grpcio (>=1.21.1)"]
httpx = ["httpx (>=0.16.0)"] httpx = ["httpx (>=0.16.0)"]
huey = ["huey (>=2)"] huey = ["huey (>=2)"]
loguru = ["loguru (>=0.5)"] loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
pure-eval = ["asttokens", "executing", "pure-eval"] pure-eval = ["asttokens", "executing", "pure-eval"]
@ -3818,13 +3818,13 @@ wsproto = ">=0.14"
[[package]] [[package]]
name = "twilio" name = "twilio"
version = "9.0.2" version = "9.0.1"
description = "Twilio API client and TwiML generator" description = "Twilio API client and TwiML generator"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.7.0"
files = [ files = [
{file = "twilio-9.0.2-py2.py3-none-any.whl", hash = "sha256:db89a8326f92240cdd8dc1dafb1d3f69169576243be9b9cbf5cf7778ecab0eed"}, {file = "twilio-9.0.1-py2.py3-none-any.whl", hash = "sha256:7df45f314140b5931199420a2a00f0466d2fd16aff2f8c3e1589a47adc9deecb"},
{file = "twilio-9.0.2.tar.gz", hash = "sha256:9450b7a9987c32146d1760c8680f92baa76b4ba570543758a4937da48ae46d77"}, {file = "twilio-9.0.1.tar.gz", hash = "sha256:4ffb63342dff9a5b24dd3f8d33e8fac2ceb55dcd10936d50456cbb44e1834e72"},
] ]
[package.dependencies] [package.dependencies]
@ -3962,33 +3962,48 @@ files = [
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.2.1" version = "1.26.18"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [ files = [
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
] ]
[package.dependencies] [package.dependencies]
pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} certifi = {version = "*", optional = true, markers = "extra == \"secure\""}
cryptography = {version = ">=1.3.4", optional = true, markers = "extra == \"secure\""}
idna = {version = ">=2.0.0", optional = true, markers = "extra == \"secure\""}
pyOpenSSL = {version = ">=0.14", optional = true, markers = "extra == \"secure\""}
PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""}
urllib3-secure-extra = {version = "*", optional = true, markers = "extra == \"secure\""}
[package.extras] [package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
h2 = ["h2 (>=4,<5)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "urllib3-secure-extra"
version = "0.1.0"
description = "Marker library to detect whether urllib3 was installed with the deprecated [secure] extra"
optional = false
python-versions = "*"
files = [
{file = "urllib3-secure-extra-0.1.0.tar.gz", hash = "sha256:ee9409cbfeb4b8609047be4c32fb4317870c602767e53fd8a41005ebe6a41dff"},
{file = "urllib3_secure_extra-0.1.0-py2.py3-none-any.whl", hash = "sha256:f7adcb108b4d12a4b26b99eb60e265d087f435052a76aefa396b6ee85e9a6ef9"},
]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.29.0" version = "0.28.0"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"}, {file = "uvicorn-0.28.0-py3-none-any.whl", hash = "sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1"},
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"}, {file = "uvicorn-0.28.0.tar.gz", hash = "sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067"},
] ]
[package.dependencies] [package.dependencies]
@ -4201,13 +4216,13 @@ files = [
[[package]] [[package]]
name = "webauthn" name = "webauthn"
version = "2.1.0" version = "2.0.0"
description = "Pythonic WebAuthn" description = "Pythonic WebAuthn"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "webauthn-2.1.0-py3-none-any.whl", hash = "sha256:9e1cf916e5ed7c01d54a6dfcc19dacbd2b87b81d2648f001b1fcbcb7aa2ff130"}, {file = "webauthn-2.0.0-py3-none-any.whl", hash = "sha256:644dc68af5caaade06be6a2a2278775e85116e92dd755ad7a49d992d51c82033"},
{file = "webauthn-2.1.0.tar.gz", hash = "sha256:b196a4246c2818820857ba195c6e6e5398c761117f2269e3d2deab11c7995fc4"}, {file = "webauthn-2.0.0.tar.gz", hash = "sha256:12cc1759da98668b8242badc37c4129df300f89d89f5c183fac80e7b33c41dfd"},
] ]
[package.dependencies] [package.dependencies]
@ -4536,4 +4551,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.12" python-versions = "~3.12"
content-hash = "04ef13e2692c158e5eda1e89876a215746c56a891a161d8434d808dc12c0fc7a" content-hash = "825f1d552ba34206f7bfd55b70bfb42bc5d769605f59410703828ae787cd0baf"

View File

@ -140,7 +140,7 @@ twilio = "*"
twisted = "*" twisted = "*"
ua-parser = "*" ua-parser = "*"
# Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937 # Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937
urllib3 = { extras = ["secure"], version = "<3" } urllib3 = { extras = ["secure"], version = "<2" }
uvicorn = { extras = ["standard"], version = "*" } uvicorn = { extras = ["standard"], version = "*" }
watchdog = "*" watchdog = "*"
webauthn = "*" webauthn = "*"

View File

@ -18512,7 +18512,6 @@ paths:
- authentik_stages_password.passwordstage - authentik_stages_password.passwordstage
- authentik_stages_prompt.prompt - authentik_stages_prompt.prompt
- authentik_stages_prompt.promptstage - authentik_stages_prompt.promptstage
- authentik_stages_source.sourcestage
- authentik_stages_user_delete.userdeletestage - authentik_stages_user_delete.userdeletestage
- authentik_stages_user_login.userloginstage - authentik_stages_user_login.userloginstage
- authentik_stages_user_logout.userlogoutstage - authentik_stages_user_logout.userlogoutstage
@ -18588,7 +18587,6 @@ paths:
* `authentik_providers_rac.racprovider` - RAC Provider * `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint * `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event * `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport * `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification * `authentik_events.notification` - Notification
@ -18802,7 +18800,6 @@ paths:
- authentik_stages_password.passwordstage - authentik_stages_password.passwordstage
- authentik_stages_prompt.prompt - authentik_stages_prompt.prompt
- authentik_stages_prompt.promptstage - authentik_stages_prompt.promptstage
- authentik_stages_source.sourcestage
- authentik_stages_user_delete.userdeletestage - authentik_stages_user_delete.userdeletestage
- authentik_stages_user_login.userloginstage - authentik_stages_user_login.userloginstage
- authentik_stages_user_logout.userlogoutstage - authentik_stages_user_logout.userlogoutstage
@ -18878,7 +18875,6 @@ paths:
* `authentik_providers_rac.racprovider` - RAC Provider * `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint * `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event * `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport * `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification * `authentik_events.notification` - Notification
@ -19926,10 +19922,6 @@ paths:
description: Number of results to return per page. description: Number of results to return per page.
schema: schema:
type: integer type: integer
- in: query
name: password_login_update_internal_password
schema:
type: boolean
- in: query - in: query
name: peer_certificate name: peer_certificate
schema: schema:
@ -27880,289 +27872,6 @@ paths:
schema: schema:
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
description: '' description: ''
/stages/source/:
get:
operationId: stages_source_list
description: SourceStage Viewset
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: query
name: resume_timeout
schema:
type: string
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: source
schema:
type: string
format: uuid
- in: query
name: stage_uuid
schema:
type: string
format: uuid
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSourceStageList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: stages_source_create
description: SourceStage Viewset
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStageRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/source/{stage_uuid}/:
get:
operationId: stages_source_retrieve
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: stages_source_update
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStageRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: stages_source_partial_update
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedSourceStageRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: stages_source_destroy
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/source/{stage_uuid}/used_by/:
get:
operationId: stages_source_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/user_delete/: /stages/user_delete/:
get: get:
operationId: stages_user_delete_list operationId: stages_user_delete_list
@ -29933,7 +29642,6 @@ components:
- authentik.enterprise - authentik.enterprise
- authentik.enterprise.audit - authentik.enterprise.audit
- authentik.enterprise.providers.rac - authentik.enterprise.providers.rac
- authentik.enterprise.stages.source
- authentik.events - authentik.events
type: string type: string
description: |- description: |-
@ -29988,7 +29696,6 @@ components:
* `authentik.enterprise` - authentik Enterprise * `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit * `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events * `authentik.events` - authentik Events
AppleChallengeResponseRequest: AppleChallengeResponseRequest:
type: object type: object
@ -31786,7 +31493,8 @@ components:
pk: pk:
type: string type: string
format: uuid format: uuid
title: Connection token uuid readOnly: true
title: Pbm uuid
provider: provider:
type: integer type: integer
provider_obj: provider_obj:
@ -31796,6 +31504,7 @@ components:
endpoint: endpoint:
type: string type: string
format: uuid format: uuid
readOnly: true
endpoint_obj: endpoint_obj:
allOf: allOf:
- $ref: '#/components/schemas/Endpoint' - $ref: '#/components/schemas/Endpoint'
@ -31807,6 +31516,7 @@ components:
required: required:
- endpoint - endpoint
- endpoint_obj - endpoint_obj
- pk
- provider - provider
- provider_obj - provider_obj
- user - user
@ -31814,17 +31524,9 @@ components:
type: object type: object
description: ConnectionToken Serializer description: ConnectionToken Serializer
properties: properties:
pk:
type: string
format: uuid
title: Connection token uuid
provider: provider:
type: integer type: integer
endpoint:
type: string
format: uuid
required: required:
- endpoint
- provider - provider
ConsentChallenge: ConsentChallenge:
type: object type: object
@ -32355,10 +32057,7 @@ components:
type: array type: array
items: items:
$ref: '#/components/schemas/ErrorDetail' $ref: '#/components/schemas/ErrorDetail'
name:
type: string
required: required:
- name
- type - type
DummyChallengeResponseRequest: DummyChallengeResponseRequest:
type: object type: object
@ -33040,7 +32739,6 @@ components:
* `authentik.enterprise` - authentik Enterprise * `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit * `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events * `authentik.events` - authentik Events
model: model:
allOf: allOf:
@ -33118,7 +32816,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider * `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint * `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event * `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport * `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification * `authentik_events.notification` - Notification
@ -33241,7 +32938,6 @@ components:
* `authentik.enterprise` - authentik Enterprise * `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit * `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events * `authentik.events` - authentik Events
model: model:
allOf: allOf:
@ -33319,7 +33015,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider * `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint * `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event * `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport * `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification * `authentik_events.notification` - Notification
@ -33782,7 +33477,8 @@ components:
logs: logs:
type: array type: array
items: items:
$ref: '#/components/schemas/LogEvent' type: object
additionalProperties: {}
readOnly: true readOnly: true
success: success:
type: boolean type: boolean
@ -34340,8 +34036,6 @@ components:
type: boolean type: boolean
application_pre: application_pre:
type: string type: string
flow_designation:
$ref: '#/components/schemas/FlowDesignationEnum'
enroll_url: enroll_url:
type: string type: string
recovery_url: recovery_url:
@ -34357,7 +34051,6 @@ components:
show_source_labels: show_source_labels:
type: boolean type: boolean
required: required:
- flow_designation
- password_fields - password_fields
- primary_action - primary_action
- show_source_labels - show_source_labels
@ -35228,10 +34921,6 @@ components:
object_uniqueness_field: object_uniqueness_field:
type: string type: string
description: Field which contains a unique Identifier. description: Field which contains a unique Identifier.
password_login_update_internal_password:
type: boolean
description: Update internal authentik password when login succeeds with
LDAP
sync_users: sync_users:
type: boolean type: boolean
sync_users_password: sync_users_password:
@ -35373,10 +35062,6 @@ components:
type: string type: string
minLength: 1 minLength: 1
description: Field which contains a unique Identifier. description: Field which contains a unique Identifier.
password_login_update_internal_password:
type: boolean
description: Update internal authentik password when login succeeds with
LDAP
sync_users: sync_users:
type: boolean type: boolean
sync_users_password: sync_users_password:
@ -35514,48 +35199,6 @@ components:
type: string type: string
required: required:
- link - link
LogEvent:
type: object
description: Single log message with all context logged.
properties:
timestamp:
type: string
format: date-time
log_level:
$ref: '#/components/schemas/LogLevelEnum'
logger:
type: string
event:
type: string
attributes:
type: object
additionalProperties: {}
required:
- attributes
- event
- log_level
- logger
- timestamp
LogLevelEnum:
enum:
- critical
- exception
- error
- warn
- warning
- info
- debug
- notset
type: string
description: |-
* `critical` - critical
* `exception` - exception
* `error` - error
* `warn` - warn
* `warning` - warning
* `info` - info
* `debug` - debug
* `notset` - notset
LoginChallengeTypes: LoginChallengeTypes:
oneOf: oneOf:
- $ref: '#/components/schemas/RedirectChallenge' - $ref: '#/components/schemas/RedirectChallenge'
@ -35687,7 +35330,6 @@ components:
- authentik_providers_rac.racprovider - authentik_providers_rac.racprovider
- authentik_providers_rac.endpoint - authentik_providers_rac.endpoint
- authentik_providers_rac.racpropertymapping - authentik_providers_rac.racpropertymapping
- authentik_stages_source.sourcestage
- authentik_events.event - authentik_events.event
- authentik_events.notificationtransport - authentik_events.notificationtransport
- authentik_events.notification - authentik_events.notification
@ -35764,7 +35406,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider * `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint * `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event * `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport * `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification * `authentik_events.notification` - Notification
@ -37699,18 +37340,6 @@ components:
required: required:
- pagination - pagination
- results - results
PaginatedSourceStageList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/SourceStage'
required:
- pagination
- results
PaginatedStageList: PaginatedStageList:
type: object type: object
properties: properties:
@ -38647,15 +38276,8 @@ components:
type: object type: object
description: ConnectionToken Serializer description: ConnectionToken Serializer
properties: properties:
pk:
type: string
format: uuid
title: Connection token uuid
provider: provider:
type: integer type: integer
endpoint:
type: string
format: uuid
PatchedConsentStageRequest: PatchedConsentStageRequest:
type: object type: object
description: ConsentStage Serializer description: ConsentStage Serializer
@ -38961,7 +38583,6 @@ components:
* `authentik.enterprise` - authentik Enterprise * `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit * `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC * `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events * `authentik.events` - authentik Events
model: model:
allOf: allOf:
@ -39039,7 +38660,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider * `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint * `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping * `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event * `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport * `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification * `authentik_events.notification` - Notification
@ -39493,10 +39113,6 @@ components:
type: string type: string
minLength: 1 minLength: 1
description: Field which contains a unique Identifier. description: Field which contains a unique Identifier.
password_login_update_internal_password:
type: boolean
description: Update internal authentik password when login succeeds with
LDAP
sync_users: sync_users:
type: boolean type: boolean
sync_users_password: sync_users_password:
@ -40626,25 +40242,6 @@ components:
impersonation: impersonation:
type: boolean type: boolean
description: Globally enable/disable impersonation. description: Globally enable/disable impersonation.
PatchedSourceStageRequest:
type: object
description: SourceStage Serializer
properties:
name:
type: string
minLength: 1
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
source:
type: string
format: uuid
resume_timeout:
type: string
minLength: 1
description: 'Amount of time a user can take to return from the source to
continue the flow (Format: hours=-1;minutes=-2;seconds=-3)'
PatchedStaticDeviceRequest: PatchedStaticDeviceRequest:
type: object type: object
description: Serializer for static authenticator devices description: Serializer for static authenticator devices
@ -41350,7 +40947,8 @@ components:
log_messages: log_messages:
type: array type: array
items: items:
$ref: '#/components/schemas/LogEvent' type: object
additionalProperties: {}
readOnly: true readOnly: true
required: required:
- log_messages - log_messages
@ -43988,74 +43586,6 @@ components:
required: required:
- name - name
- slug - slug
SourceStage:
type: object
description: SourceStage Serializer
properties:
pk:
type: string
format: uuid
readOnly: true
title: Stage uuid
name:
type: string
component:
type: string
description: Get object type so that we know how to edit the object
readOnly: true
verbose_name:
type: string
description: Return object's verbose_name
readOnly: true
verbose_name_plural:
type: string
description: Return object's plural verbose_name
readOnly: true
meta_model_name:
type: string
description: Return internal model name
readOnly: true
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSet'
source:
type: string
format: uuid
resume_timeout:
type: string
description: 'Amount of time a user can take to return from the source to
continue the flow (Format: hours=-1;minutes=-2;seconds=-3)'
required:
- component
- meta_model_name
- name
- pk
- source
- verbose_name
- verbose_name_plural
SourceStageRequest:
type: object
description: SourceStage Serializer
properties:
name:
type: string
minLength: 1
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
source:
type: string
format: uuid
resume_timeout:
type: string
minLength: 1
description: 'Amount of time a user can take to return from the source to
continue the flow (Format: hours=-1;minutes=-2;seconds=-3)'
required:
- name
- source
SourceType: SourceType:
type: object type: object
description: Serializer for SourceType description: Serializer for SourceType
@ -44364,7 +43894,7 @@ components:
messages: messages:
type: array type: array
items: items:
$ref: '#/components/schemas/LogEvent' type: string
required: required:
- description - description
- duration - duration
@ -45654,10 +45184,6 @@ components:
type: string type: string
description: Get latest version from cache description: Get latest version from cache
readOnly: true readOnly: true
version_latest_valid:
type: boolean
description: Check if latest version is valid
readOnly: true
build_hash: build_hash:
type: string type: string
description: Get build hash, if version is not latest or released description: Get build hash, if version is not latest or released
@ -45671,7 +45197,6 @@ components:
- outdated - outdated
- version_current - version_current
- version_latest - version_latest
- version_latest_valid
WebAuthnDevice: WebAuthnDevice:
type: object type: object
description: Serializer for WebAuthn authenticator devices description: Serializer for WebAuthn authenticator devices

View File

@ -128,7 +128,6 @@ class TestSourceLDAPSamba(SeleniumTestCase):
base_dn="dc=test,dc=goauthentik,dc=io", base_dn="dc=test,dc=goauthentik,dc=io",
additional_user_dn="ou=users", additional_user_dn="ou=users",
additional_group_dn="ou=groups", additional_group_dn="ou=groups",
password_login_update_internal_password=True,
) )
source.property_mappings.set( source.property_mappings.set(
LDAPPropertyMapping.objects.filter( LDAPPropertyMapping.objects.filter(

View File

@ -10,7 +10,6 @@ from kubernetes.client.exceptions import OpenApiException
from authentik.core.tests.utils import create_test_flow from authentik.core.tests.utils import create_test_flow
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.k8s.service import ServiceReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
from authentik.outposts.tasks import outpost_connection_discovery from authentik.outposts.tasks import outpost_connection_discovery
@ -92,35 +91,6 @@ class OutpostKubernetesTests(TestCase):
deployment_reconciler.delete(deployment_reconciler.get_reference_object()) deployment_reconciler.delete(deployment_reconciler.get_reference_object())
@pytest.mark.timeout(120)
def test_service_reconciler(self):
"""test that service requires update"""
controller = ProxyKubernetesController(self.outpost, self.service_connection)
service_reconciler = ServiceReconciler(controller)
self.assertIsNotNone(service_reconciler.retrieve())
config = self.outpost.config
config.kubernetes_service_type = "NodePort"
config.kubernetes_json_patches = {
"service": [
{
"op": "add",
"path": "/spec/ipFamilyPolicy",
"value": "PreferDualStack",
}
]
}
self.outpost.config = config
with self.assertRaises(NeedsUpdate):
service_reconciler.reconcile(
service_reconciler.retrieve(),
service_reconciler.get_reference_object(),
)
service_reconciler.delete(service_reconciler.get_reference_object())
@pytest.mark.timeout(120) @pytest.mark.timeout(120)
def test_controller_rename(self): def test_controller_rename(self):
"""test that objects get deleted and re-created with new names""" """test that objects get deleted and re-created with new names"""

View File

@ -26,6 +26,12 @@ class TestProxyKubernetes(TestCase):
outpost_connection_discovery() outpost_connection_discovery()
self.controller = None self.controller = None
def tearDown(self) -> None:
if self.controller:
for log in self.controller.down_with_logs():
LOGGER.info(log)
return super().tearDown()
@pytest.mark.timeout(120) @pytest.mark.timeout(120)
def test_kubernetes_controller_static(self): def test_kubernetes_controller_static(self):
"""Test Kubernetes Controller""" """Test Kubernetes Controller"""

View File

@ -6,23 +6,23 @@
"": { "": {
"name": "@goauthentik/web-tests", "name": "@goauthentik/web-tests",
"dependencies": { "dependencies": {
"chromedriver": "^123.0.1" "chromedriver": "^122.0.6"
}, },
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.4.0", "@typescript-eslint/parser": "^7.2.0",
"@wdio/cli": "^8.35.1", "@wdio/cli": "^8.33.1",
"@wdio/local-runner": "^8.35.1", "@wdio/local-runner": "^8.33.1",
"@wdio/mocha-framework": "^8.35.0", "@wdio/mocha-framework": "^8.33.1",
"@wdio/spec-reporter": "^8.32.4", "@wdio/spec-reporter": "^8.32.4",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.25.0", "eslint-plugin-sonarjs": "^0.24.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.3", "typescript": "^5.4.2",
"wdio-wait-for": "^3.0.11" "wdio-wait-for": "^3.0.11"
}, },
"engines": { "engines": {
@ -889,13 +889,10 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.11.28", "version": "20.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz",
"integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==",
"devOptional": true, "devOptional": true
"dependencies": {
"undici-types": "~5.26.4"
}
}, },
"node_modules/@types/normalize-package-data": { "node_modules/@types/normalize-package-data": {
"version": "2.4.4", "version": "2.4.4",
@ -955,16 +952,16 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz",
"integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.5.1", "@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "7.4.0", "@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/type-utils": "7.4.0", "@typescript-eslint/type-utils": "7.2.0",
"@typescript-eslint/utils": "7.4.0", "@typescript-eslint/utils": "7.2.0",
"@typescript-eslint/visitor-keys": "7.4.0", "@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.2.4", "ignore": "^5.2.4",
@ -973,7 +970,7 @@
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -990,19 +987,19 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
"integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "7.4.0", "@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/types": "7.4.0", "@typescript-eslint/types": "7.2.0",
"@typescript-eslint/typescript-estree": "7.4.0", "@typescript-eslint/typescript-estree": "7.2.0",
"@typescript-eslint/visitor-keys": "7.4.0", "@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1018,16 +1015,16 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz",
"integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.4.0", "@typescript-eslint/types": "7.2.0",
"@typescript-eslint/visitor-keys": "7.4.0" "@typescript-eslint/visitor-keys": "7.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1035,18 +1032,18 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz",
"integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "7.4.0", "@typescript-eslint/typescript-estree": "7.2.0",
"@typescript-eslint/utils": "7.4.0", "@typescript-eslint/utils": "7.2.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1062,12 +1059,12 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz",
"integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1075,13 +1072,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz",
"integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.4.0", "@typescript-eslint/types": "7.2.0",
"@typescript-eslint/visitor-keys": "7.4.0", "@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1090,7 +1087,7 @@
"ts-api-utils": "^1.0.1" "ts-api-utils": "^1.0.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1127,21 +1124,21 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz",
"integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12", "@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0", "@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "7.4.0", "@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/types": "7.4.0", "@typescript-eslint/types": "7.2.0",
"@typescript-eslint/typescript-estree": "7.4.0", "@typescript-eslint/typescript-estree": "7.2.0",
"semver": "^7.5.4" "semver": "^7.5.4"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1152,16 +1149,16 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "7.4.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz",
"integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.4.0", "@typescript-eslint/types": "7.2.0",
"eslint-visitor-keys": "^3.4.1" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^16.0.0 || >=18.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -1189,19 +1186,19 @@
} }
}, },
"node_modules/@wdio/cli": { "node_modules/@wdio/cli": {
"version": "8.35.1", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.35.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.33.1.tgz",
"integrity": "sha512-cdFmd6P/eQJdP2lChQ+Fa9b1c2p0bDIPmetVHGCuHiW8ZPkanrvBFtHMUhMu44a1koni9LvN/hu7vIJ/aAC+Rg==", "integrity": "sha512-Ngt5R6YAmErkSKnWLWt1JilLIKDPIB0P93bzQhb9bQhmg1arFBcl75uiwe6kf6T355vzcNslMaEJyeuqGChmCg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.1", "@types/node": "^20.1.1",
"@vitest/snapshot": "^1.2.1", "@vitest/snapshot": "^1.2.1",
"@wdio/config": "8.35.0", "@wdio/config": "8.33.1",
"@wdio/globals": "8.35.1", "@wdio/globals": "8.33.1",
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/protocols": "8.32.0", "@wdio/protocols": "8.32.0",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0", "@wdio/utils": "8.33.1",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"chalk": "^5.2.0", "chalk": "^5.2.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
@ -1216,7 +1213,7 @@
"lodash.union": "^4.6.0", "lodash.union": "^4.6.0",
"read-pkg-up": "10.0.0", "read-pkg-up": "10.0.0",
"recursive-readdir": "^2.2.3", "recursive-readdir": "^2.2.3",
"webdriverio": "8.35.1", "webdriverio": "8.33.1",
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
@ -1239,14 +1236,14 @@
} }
}, },
"node_modules/@wdio/config": { "node_modules/@wdio/config": {
"version": "8.35.0", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.35.0.tgz", "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.33.1.tgz",
"integrity": "sha512-I36sBPMl/+LCyQ3Pwb8gGQM6KxwmUfhOPp16TxN21Qo/Bc0fZfyGIg6KevmRu4DuqpGUm5MMVSfyPhLUkMk3Cg==", "integrity": "sha512-JB7+tRkEsDJ4QAgJIZ3AaZvlp8pfBH6A5cKcGsaOuLVYMnsRPVkEGQc6n2akN9EPlDA2UjyrPOX6KZHbsSty7w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0", "@wdio/utils": "8.33.1",
"decamelize": "^6.0.0", "decamelize": "^6.0.0",
"deepmerge-ts": "^5.0.0", "deepmerge-ts": "^5.0.0",
"glob": "^10.2.2", "glob": "^10.2.2",
@ -1257,28 +1254,28 @@
} }
}, },
"node_modules/@wdio/globals": { "node_modules/@wdio/globals": {
"version": "8.35.1", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.35.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.33.1.tgz",
"integrity": "sha512-T3IUFcKXRU9WWleAV72DGFWUiXSSr8SBvpc2cUJrvZ5Je9R2gEsrts5eHCY7amXtfeylfMgy5EayGMajgcna6A==", "integrity": "sha512-1ud9oq7n9MMNywS/FoMRRWqW6uhcoxgnpXoGeLE2Tr+4f937ABOl+sfZgjycXujyvR7yTL8AROOYajp1Yuv1Xg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"expect-webdriverio": "^4.11.2", "expect-webdriverio": "^4.11.2",
"webdriverio": "8.35.1" "webdriverio": "8.33.1"
} }
}, },
"node_modules/@wdio/local-runner": { "node_modules/@wdio/local-runner": {
"version": "8.35.1", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.35.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.33.1.tgz",
"integrity": "sha512-PG+bADoY5VoWPmAfRi030rtxbFj68MVPlcwEN0dN1lDdYKz1ATzzGUK12sqCgGz1ktcC7sQzmJZVBklzbvn3mQ==", "integrity": "sha512-eQp12wHIkyh5zl9fun1qjv5Qvf4mCHPgLs5sKbfo3OK4LadzmD4/QNvDG8DYq/9cyuhVvnHgbLQ3XAnkoPde3w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/repl": "8.24.12", "@wdio/repl": "8.24.12",
"@wdio/runner": "8.35.1", "@wdio/runner": "8.33.1",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"async-exit-hook": "^2.0.1", "async-exit-hook": "^2.0.1",
"split2": "^4.1.0", "split2": "^4.1.0",
@ -1316,16 +1313,16 @@
} }
}, },
"node_modules/@wdio/mocha-framework": { "node_modules/@wdio/mocha-framework": {
"version": "8.35.0", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.35.0.tgz", "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.33.1.tgz",
"integrity": "sha512-riO3aMgvGdFFRMpyMk5m480V+mi5EcKk6cjT1TB9L5XEN7Mo/8qthBw9CLgFCZkr4KlR40hgPKSZFHE0rH2GpQ==", "integrity": "sha512-CxYLE22+tgnMnruElvDGJGR+dE0pxvMZ95agIUYYen69DJ705a74XtTR6zX9COWu6RooBezHgEs3fXev0XL79Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0", "@wdio/utils": "8.33.1",
"mocha": "^10.0.0" "mocha": "^10.0.0"
}, },
"engines": { "engines": {
@ -1367,22 +1364,22 @@
} }
}, },
"node_modules/@wdio/runner": { "node_modules/@wdio/runner": {
"version": "8.35.1", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.35.1.tgz", "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.33.1.tgz",
"integrity": "sha512-5F6cbOYeZjF34Vsnycp5JPnDljI52fmyxsV2O/L3h6F2+83YXpbsqBplw/2G24JtIUudV7VOY/38bUicn1OyXg==", "integrity": "sha512-i0eRwMCePKkQocWsdkPQpBb1jELyNR5JCwnmOgM3g9fQI6KAf5D4oEUkNDFL/vD4UtgbSRmux7b7j5G01VvuqQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.11.28", "@types/node": "^20.1.0",
"@wdio/config": "8.35.0", "@wdio/config": "8.33.1",
"@wdio/globals": "8.35.1", "@wdio/globals": "8.33.1",
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0", "@wdio/utils": "8.33.1",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.0.0",
"expect-webdriverio": "^4.12.0", "expect-webdriverio": "^4.11.2",
"gaze": "^1.1.3", "gaze": "^1.1.2",
"webdriver": "8.35.0", "webdriver": "8.33.1",
"webdriverio": "8.35.1" "webdriverio": "8.33.1"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"
@ -1429,9 +1426,9 @@
} }
}, },
"node_modules/@wdio/utils": { "node_modules/@wdio/utils": {
"version": "8.35.0", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.35.0.tgz", "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.33.1.tgz",
"integrity": "sha512-9KCyn4aS+9tWfthnUkNFVe52AM6QrLGAeIxgGxNlzTAcQGl7jjwdDM7aSK0RjLkWI3a/88DRH21mN/t2LGDmPQ==", "integrity": "sha512-W0ArrZbs4M23POv8+FPsgHDFxg+wwklfZgLSsjVq2kpCmBCfIPxKSAOgTo/XrcH4We/OnshgBzxLcI+BHDgi4w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@puppeteer/browsers": "^1.6.0", "@puppeteer/browsers": "^1.6.0",
@ -2084,9 +2081,9 @@
} }
}, },
"node_modules/chromedriver": { "node_modules/chromedriver": {
"version": "123.0.1", "version": "122.0.6",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.1.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.6.tgz",
"integrity": "sha512-YQUIP/zdlzDIRCZNCv6rEVDSY4RAxo/tDL0OiGPPuai+z8unRNqJr/9V6XTBypVFyDheXNalKt9QxEqdMPuLAQ==", "integrity": "sha512-Q0r+QlUtiJWMQ5HdYaFa0CtBmLFq3n5JWfmq9mOC00UMBvWxku09gUkvBt457QnYfTM/XHqY/HTFOxHvATnTmA==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@testim/chrome-version": "^1.1.4", "@testim/chrome-version": "^1.1.4",
@ -2691,9 +2688,9 @@
} }
}, },
"node_modules/devtools-protocol": { "node_modules/devtools-protocol": {
"version": "0.0.1273771", "version": "0.0.1263784",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1273771.tgz", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1263784.tgz",
"integrity": "sha512-QDbb27xcTVReQQW/GHJsdQqGKwYBE7re7gxehj467kKP2DKuYBUj6i2k5LRiAC66J1yZG/9gsxooz/s9pcm0Og==", "integrity": "sha512-k0SCZMwj587w4F8QYbP5iIbSonL6sd3q8aVJch036r9Tv2t9b5/Oq7AiJ/FJvRuORm/pJNXZtrdNNWlpRnl56A==",
"dev": true "dev": true
}, },
"node_modules/diff": { "node_modules/diff": {
@ -3114,9 +3111,9 @@
} }
}, },
"node_modules/eslint-plugin-sonarjs": { "node_modules/eslint-plugin-sonarjs": {
"version": "0.25.0", "version": "0.24.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz",
"integrity": "sha512-DaZOtpUucEZbvowgKxVFwICV6r0h7jSCAx0IHICvCowP+etFussnhtaiCPSnYAuwVJ+P/6UFUhkv7QJklpXFyA==", "integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=16" "node": ">=16"
@ -3413,9 +3410,9 @@
} }
}, },
"node_modules/expect-webdriverio": { "node_modules/expect-webdriverio": {
"version": "4.12.1", "version": "4.11.2",
"resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.12.1.tgz", "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.11.2.tgz",
"integrity": "sha512-jTfyC2bJbPNw4c8MlEwZNX7SjtPbZ73ysJvr/OGKA9mSKC+toyjU2eMNzHlt9WZO5+wl0RDS1dR7VxHXeu7+zA==", "integrity": "sha512-PK8lrQmRhK8NRtE8i/CJsnKS/QGrHULQW5EfmyKxIiLHnXd7t8dX0dWJn/fiYVtrPKBUyXSL6h52QqvZVc9yGQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@vitest/snapshot": "^1.2.2", "@vitest/snapshot": "^1.2.2",
@ -3699,9 +3696,9 @@
"dev": true "dev": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.6", "version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -8620,9 +8617,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.4.3", "version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -8657,12 +8654,6 @@
"through": "^2.3.8" "through": "^2.3.8"
} }
}, },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"devOptional": true
},
"node_modules/universalify": { "node_modules/universalify": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@ -8886,18 +8877,18 @@
} }
}, },
"node_modules/webdriver": { "node_modules/webdriver": {
"version": "8.35.0", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.35.0.tgz", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.33.1.tgz",
"integrity": "sha512-D13EroddIXDqdq3jgO8j6sorgTWqTwEiTqwlDoJizpRIgHGBy+UjkNM7XW1yVcvt8gsD2Dei2LQth2tJEnu5Ng==", "integrity": "sha512-QREF4c08omN9BPh3QDmz5h+OEvjdzDliuEcrDuXoDnHSMxIj1rsonzsgRaM2PXhFZuPeMIiKZYqc7Qg9BGbh6A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"@wdio/config": "8.35.0", "@wdio/config": "8.33.1",
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/protocols": "8.32.0", "@wdio/protocols": "8.32.0",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0", "@wdio/utils": "8.33.1",
"deepmerge-ts": "^5.1.0", "deepmerge-ts": "^5.1.0",
"got": "^12.6.1", "got": "^12.6.1",
"ky": "^0.33.0", "ky": "^0.33.0",
@ -8908,23 +8899,23 @@
} }
}, },
"node_modules/webdriverio": { "node_modules/webdriverio": {
"version": "8.35.1", "version": "8.33.1",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.35.1.tgz", "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.33.1.tgz",
"integrity": "sha512-YAuKR4JERGiMqCJmm5fEVZ160iiFPyupwALqfXfzrYVcEmKltKPFY/oUCArmi6Uzqd+Sa2Kp9WZtz2Eu1R76JA==", "integrity": "sha512-1DsF8sx1a46AoVYCUpEwJYU74iBAW/U2H5r6p+60ct7dIiFmxmc4uCbOqtf7NLOTgrIzAOaRnT0EsrRICpg5Qw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "^20.1.0", "@types/node": "^20.1.0",
"@wdio/config": "8.35.0", "@wdio/config": "8.33.1",
"@wdio/logger": "8.28.0", "@wdio/logger": "8.28.0",
"@wdio/protocols": "8.32.0", "@wdio/protocols": "8.32.0",
"@wdio/repl": "8.24.12", "@wdio/repl": "8.24.12",
"@wdio/types": "8.32.4", "@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0", "@wdio/utils": "8.33.1",
"archiver": "^7.0.0", "archiver": "^7.0.0",
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"css-shorthand-properties": "^1.1.1", "css-shorthand-properties": "^1.1.1",
"css-value": "^0.0.1", "css-value": "^0.0.1",
"devtools-protocol": "^0.0.1273771", "devtools-protocol": "^0.0.1263784",
"grapheme-splitter": "^1.0.2", "grapheme-splitter": "^1.0.2",
"import-meta-resolve": "^4.0.0", "import-meta-resolve": "^4.0.0",
"is-plain-obj": "^4.1.0", "is-plain-obj": "^4.1.0",
@ -8936,7 +8927,7 @@
"resq": "^1.9.1", "resq": "^1.9.1",
"rgb2hex": "0.2.5", "rgb2hex": "0.2.5",
"serialize-error": "^11.0.1", "serialize-error": "^11.0.1",
"webdriver": "8.35.0" "webdriver": "8.33.1"
}, },
"engines": { "engines": {
"node": "^16.13 || >=18" "node": "^16.13 || >=18"

View File

@ -4,19 +4,19 @@
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.4.0", "@typescript-eslint/parser": "^7.2.0",
"@wdio/cli": "^8.35.1", "@wdio/cli": "^8.33.1",
"@wdio/local-runner": "^8.35.1", "@wdio/local-runner": "^8.33.1",
"@wdio/mocha-framework": "^8.35.0", "@wdio/mocha-framework": "^8.33.1",
"@wdio/spec-reporter": "^8.32.4", "@wdio/spec-reporter": "^8.32.4",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-sonarjs": "^0.25.0", "eslint-plugin-sonarjs": "^0.24.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.3", "typescript": "^5.4.2",
"wdio-wait-for": "^3.0.11" "wdio-wait-for": "^3.0.11"
}, },
"scripts": { "scripts": {
@ -32,6 +32,6 @@
"node": ">=20" "node": ">=20"
}, },
"dependencies": { "dependencies": {
"chromedriver": "^123.0.1" "chromedriver": "^122.0.6"
} }
} }

View File

@ -55,15 +55,15 @@ for (const [source, rawdest, strip] of otherFiles) {
// This starts the definitions used for esbuild: Our targets, our arguments, the function for running a build, and three // This starts the definitions used for esbuild: Our targets, our arguments, the function for running a build, and three
// options for building: watching, building, and building the proxy. // options for building: watching, building, and building the proxy.
// Ordered by largest to smallest interface to build even faster
const interfaces = [ const interfaces = [
["admin/AdminInterface/AdminInterface.ts", "admin"],
["user/UserInterface.ts", "user"],
["flow/FlowInterface.ts", "flow"],
["standalone/api-browser/index.ts", "standalone/api-browser"],
["enterprise/rac/index.ts", "enterprise/rac"],
["standalone/loading/index.ts", "standalone/loading"],
["polyfill/poly.ts", "."], ["polyfill/poly.ts", "."],
["standalone/loading/index.ts", "standalone/loading"],
["flow/FlowInterface.ts", "flow"],
["user/UserInterface.ts", "user"],
["enterprise/rac/index.ts", "enterprise/rac"],
["standalone/api-browser/index.ts", "standalone/api-browser"],
["admin/AdminInterface/AdminInterface.ts", "admin"],
]; ];
const baseArgs = { const baseArgs = {
@ -80,30 +80,27 @@ const baseArgs = {
format: "esm", format: "esm",
}; };
async function buildOneSource(source, dest) { function buildAuthentik(interfaces) {
for (const [source, dest] of interfaces) {
const DIST = path.join(__dirname, "./dist", dest); const DIST = path.join(__dirname, "./dist", dest);
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`); console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
try { try {
const start = Date.now(); const start = Date.now();
await esbuild.build({ esbuild.buildSync({
...baseArgs, ...baseArgs,
entryPoints: [`./src/${source}`], entryPoints: [`./src/${source}`],
outdir: DIST, outdir: DIST,
}); });
const end = Date.now(); const end = Date.now();
console.log( console.log(
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${ `[${new Date(end).toISOString()}] Finished build for target ${source} in ${Date.now() - start}ms`,
Date.now() - start
}ms`,
); );
} catch (exc) { } catch (exc) {
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`); console.error(
`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`,
);
} }
} }
async function buildAuthentik(interfaces) {
await Promise.allSettled(interfaces.map(([source, dest]) => buildOneSource(source, dest)));
} }
let timeoutId = null; let timeoutId = null;
@ -141,11 +138,9 @@ if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] ===
}); });
} else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) { } else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) {
// There's no watch-for-proxy, sorry. // There's no watch-for-proxy, sorry.
await buildAuthentik( buildAuthentik(interfaces.slice(0, 2));
interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
);
process.exit(0); process.exit(0);
} else { } else {
// And the fallback: just build it. // And the fallback: just build it.
await buildAuthentik(interfaces); buildAuthentik(interfaces);
} }

View File

@ -1,17 +0,0 @@
### 2024-03-26T09:25:06-0700
Split the tsconfig file into a base and build variant.
Lesson: This lesson is stored here and not in a comment in tsconfig.json because
JSON doesn't like comments. Doug Crockford's purity requirement has doomed an
entire generation to keeping its human-facing meta somewhere other than in the
file where it belongs.
Lesson: The `extend` command of tsconfig has an unexpected behavior. It is
neither a merge or a replace, but some mixture of the two. The buildfile's
`compilerOptions` is not a full replacement; instead, each of _its_ top-level
fields is a replacement for what is found in the basefile. So while you don't
need to include _everything_ in a `compilerOptions` field if you want to change
one thing, if you want to modify _one_ path in `compilerOptions.path`, you must
include the entire `compilerOptions.path` collection in your buildfile.
g

1299
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,8 @@
"build": "run-s build-locales esbuild:build", "build": "run-s build-locales esbuild:build",
"build-proxy": "run-s build-locales esbuild:build-proxy", "build-proxy": "run-s build-locales esbuild:build-proxy",
"watch": "run-s build-locales esbuild:watch", "watch": "run-s build-locales esbuild:watch",
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=8192' eslint . --max-warnings 0 --fix", "lint": "eslint . --max-warnings 0 --fix",
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=8192' node scripts/eslint-precommit.mjs", "lint:precommit": "node scripts/eslint-precommit.mjs",
"lint:spelling": "node scripts/check-spelling.mjs", "lint:spelling": "node scripts/check-spelling.mjs",
"lit-analyse": "lit-analyzer src", "lit-analyse": "lit-analyzer src",
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
@ -32,29 +32,29 @@
"dependencies": { "dependencies": {
"@codemirror/lang-html": "^6.4.8", "@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.5", "@codemirror/lang-python": "^6.1.4",
"@codemirror/lang-xml": "^6.1.0", "@codemirror/lang-xml": "^6.1.0",
"@codemirror/legacy-modes": "^6.3.3", "@codemirror/legacy-modes": "^6.3.3",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.5", "@formatjs/intl-listformat": "^7.5.5",
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2024.2.2-1711643691", "@goauthentik/api": "^2024.2.2-1709583949",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/reactive-element": "^2.0.4",
"@lit/context": "^1.1.0", "@lit/context": "^1.1.0",
"@lit/localize": "^0.12.1", "@lit/localize": "^0.12.1",
"@lit/reactive-element": "^2.0.4",
"@open-wc/lit-helpers": "^0.7.0", "@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.109.0", "@sentry/browser": "^7.106.1",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.2", "chart.js": "^4.4.2",
"chartjs-adapter-moment": "^1.0.1", "chartjs-adapter-moment": "^1.0.1",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0", "construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.36.1", "core-js": "^3.36.0",
"country-flag-icons": "^1.5.10", "country-flag-icons": "^1.5.9",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"guacamole-common-js": "^1.5.0", "guacamole-common-js": "^1.5.0",
"lit": "^3.1.2", "lit": "^3.1.2",
@ -68,19 +68,18 @@
"yaml": "^2.4.1" "yaml": "^2.4.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.3", "@babel/core": "^7.24.0",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.1", "@babel/plugin-proposal-decorators": "^7.24.0",
"@babel/plugin-transform-private-methods": "^7.24.1", "@babel/plugin-transform-private-methods": "^7.23.3",
"@babel/plugin-transform-private-property-in-object": "^7.24.1", "@babel/plugin-transform-private-property-in-object": "^7.23.4",
"@babel/plugin-transform-runtime": "^7.24.3", "@babel/plugin-transform-runtime": "^7.24.0",
"@babel/preset-env": "^7.24.3", "@babel/preset-env": "^7.24.0",
"@babel/preset-typescript": "^7.24.1", "@babel/preset-typescript": "^7.23.3",
"@hcaptcha/types": "^1.0.3", "@hcaptcha/types": "^1.0.3",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2", "@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5", "@spotlightjs/spotlight": "^1.2.12",
"@spotlightjs/spotlight": "^1.2.16",
"@storybook/addon-essentials": "^7.6.17", "@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17", "@storybook/addon-links": "^7.6.17",
"@storybook/api": "^7.6.17", "@storybook/api": "^7.6.17",
@ -91,47 +90,48 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41", "@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.9", "@types/grecaptcha": "^3.0.8",
"@types/guacamole-common-js": "1.5.2", "@types/guacamole-common-js": "1.5.2",
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",
"@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.4.0", "@typescript-eslint/parser": "^7.2.0",
"@rollup/plugin-replace": "^5.0.5",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esbuild": "^0.20.2", "esbuild": "^0.20.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0", "eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^0.25.0", "eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-storybook": "^0.8.0", "eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
"glob": "^10.3.12", "glob": "^10.3.10",
"lit-analyzer": "^2.0.3", "lit-analyzer": "^2.0.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"pseudolocale": "^2.0.0", "pseudolocale": "^2.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.17", "storybook": "^7.6.17",
"storybook-addon-mock": "^4.3.0", "storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.2", "ts-lit-plugin": "^2.0.2",
"tslib": "^2.6.2", "tslib": "^2.6.2",
"turnstile-types": "^1.2.0", "turnstile-types": "^1.2.0",
"typescript": "^5.4.3", "typescript": "^5.4.2",
"vite-tsconfig-paths": "^4.3.2" "vite-tsconfig-paths": "^4.3.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/darwin-arm64": "^0.20.1", "@esbuild/darwin-arm64": "^0.20.1",
"@esbuild/linux-amd64": "^0.18.11", "@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.20.1", "@esbuild/linux-arm64": "^0.20.1",
"@rollup/rollup-darwin-arm64": "4.13.2", "@rollup/rollup-darwin-arm64": "4.13.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.2", "@rollup/rollup-linux-arm64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-gnu": "4.13.2" "@rollup/rollup-linux-x64-gnu": "4.13.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"

View File

@ -1,155 +1,266 @@
import "@goauthentik/admin/admin-overview/AdminOverviewPage"; import "@goauthentik/admin/admin-overview/AdminOverviewPage";
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route"; import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import { RawRoute, makeRoute } from "@goauthentik/elements/router/routeUtils";
import { html } from "lit"; import { html } from "lit";
export const ROUTES: Route[] = [ export const _ROUTES: RawRoute[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/administration/overview"), ["^/$", "/administration/overview"],
new Route(new RegExp("^#.*")).redirect("/administration/overview"), ["^#.*", "/administration/overview"],
new Route(new RegExp("^/library$")).redirect("/if/user/", true), ["^/library$", ["/if/user/", true]],
// statically imported since this is the default route // statically imported since this is the default route
new Route(new RegExp("^/administration/overview$"), async () => { [
"^/administration/overview$",
async () => {
return html`<ak-admin-overview></ak-admin-overview>`; return html`<ak-admin-overview></ak-admin-overview>`;
}), },
new Route(new RegExp("^/administration/dashboard/users$"), async () => { ],
[
"^/administration/dashboard/users$",
async () => {
await import("@goauthentik/admin/admin-overview/DashboardUserPage"); await import("@goauthentik/admin/admin-overview/DashboardUserPage");
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`; return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
}), },
new Route(new RegExp("^/administration/system-tasks$"), async () => { ],
[
"^/administration/system-tasks$",
async () => {
await import("@goauthentik/admin/system-tasks/SystemTaskListPage"); await import("@goauthentik/admin/system-tasks/SystemTaskListPage");
return html`<ak-system-task-list></ak-system-task-list>`; return html`<ak-system-task-list></ak-system-task-list>`;
}), },
new Route(new RegExp("^/core/providers$"), async () => { ],
[
"^/core/providers$",
async () => {
await import("@goauthentik/admin/providers/ProviderListPage"); await import("@goauthentik/admin/providers/ProviderListPage");
return html`<ak-provider-list></ak-provider-list>`; return html`<ak-provider-list></ak-provider-list>`;
}), },
new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => { ],
[
`^/core/providers/(?<id>${ID_REGEX}])$`,
async (args) => {
await import("@goauthentik/admin/providers/ProviderViewPage"); await import("@goauthentik/admin/providers/ProviderViewPage");
return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`; return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
}), },
new Route(new RegExp("^/core/applications$"), async () => { ],
[
"^/core/applications$",
async () => {
await import("@goauthentik/admin/applications/ApplicationListPage"); await import("@goauthentik/admin/applications/ApplicationListPage");
return html`<ak-application-list></ak-application-list>`; return html`<ak-application-list></ak-application-list>`;
}), },
new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => { ],
[
`^/core/applications/(?<slug>${SLUG_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/applications/ApplicationViewPage"); await import("@goauthentik/admin/applications/ApplicationViewPage");
return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`; return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`;
}), },
new Route(new RegExp("^/core/sources$"), async () => { ],
[
"^/core/sources$",
async () => {
await import("@goauthentik/admin/sources/SourceListPage"); await import("@goauthentik/admin/sources/SourceListPage");
return html`<ak-source-list></ak-source-list>`; return html`<ak-source-list></ak-source-list>`;
}), },
new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => { ],
[
`^/core/sources/(?<slug>${SLUG_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/sources/SourceViewPage"); await import("@goauthentik/admin/sources/SourceViewPage");
return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`; return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
}), },
new Route(new RegExp("^/core/property-mappings$"), async () => { ],
[
"^/core/property-mappings$",
async () => {
await import("@goauthentik/admin/property-mappings/PropertyMappingListPage"); await import("@goauthentik/admin/property-mappings/PropertyMappingListPage");
return html`<ak-property-mapping-list></ak-property-mapping-list>`; return html`<ak-property-mapping-list></ak-property-mapping-list>`;
}), },
new Route(new RegExp("^/core/tokens$"), async () => { ],
[
"^/core/tokens$",
async () => {
await import("@goauthentik/admin/tokens/TokenListPage"); await import("@goauthentik/admin/tokens/TokenListPage");
return html`<ak-token-list></ak-token-list>`; return html`<ak-token-list></ak-token-list>`;
}), },
new Route(new RegExp("^/core/brands"), async () => { ],
[
"^/core/brands",
async () => {
await import("@goauthentik/admin/brands/BrandListPage"); await import("@goauthentik/admin/brands/BrandListPage");
return html`<ak-brand-list></ak-brand-list>`; return html`<ak-brand-list></ak-brand-list>`;
}), },
new Route(new RegExp("^/policy/policies$"), async () => { ],
[
"^/policy/policies$",
async () => {
await import("@goauthentik/admin/policies/PolicyListPage"); await import("@goauthentik/admin/policies/PolicyListPage");
return html`<ak-policy-list></ak-policy-list>`; return html`<ak-policy-list></ak-policy-list>`;
}), },
new Route(new RegExp("^/policy/reputation$"), async () => { ],
[
"^/policy/reputation$",
async () => {
await import("@goauthentik/admin/policies/reputation/ReputationListPage"); await import("@goauthentik/admin/policies/reputation/ReputationListPage");
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`; return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
}), },
new Route(new RegExp("^/identity/groups$"), async () => { ],
[
"^/identity/groups$",
async () => {
await import("@goauthentik/admin/groups/GroupListPage"); await import("@goauthentik/admin/groups/GroupListPage");
return html`<ak-group-list></ak-group-list>`; return html`<ak-group-list></ak-group-list>`;
}), },
new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => { ],
[
`^/identity/groups/(?<uuid>${UUID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/groups/GroupViewPage"); await import("@goauthentik/admin/groups/GroupViewPage");
return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`; return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`;
}), },
new Route(new RegExp("^/identity/users$"), async () => { ],
[
"^/identity/users$",
async () => {
await import("@goauthentik/admin/users/UserListPage"); await import("@goauthentik/admin/users/UserListPage");
return html`<ak-user-list></ak-user-list>`; return html`<ak-user-list></ak-user-list>`;
}), },
new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => { ],
[
`^/identity/users/(?<id>${ID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/users/UserViewPage"); await import("@goauthentik/admin/users/UserViewPage");
return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`; return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
}), },
new Route(new RegExp("^/identity/roles$"), async () => { ],
[
"^/identity/roles$",
async () => {
await import("@goauthentik/admin/roles/RoleListPage"); await import("@goauthentik/admin/roles/RoleListPage");
return html`<ak-role-list></ak-role-list>`; return html`<ak-role-list></ak-role-list>`;
}), },
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => { ],
[
`^/identity/roles/(?<id>${UUID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/roles/RoleViewPage"); await import("@goauthentik/admin/roles/RoleViewPage");
return html`<ak-role-view roleId=${args.id}></ak-role-view>`; return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
}), },
new Route(new RegExp("^/flow/stages/invitations$"), async () => { ],
[
"^/flow/stages/invitations$",
async () => {
await import("@goauthentik/admin/stages/invitation/InvitationListPage"); await import("@goauthentik/admin/stages/invitation/InvitationListPage");
return html`<ak-stage-invitation-list></ak-stage-invitation-list>`; return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
}), },
new Route(new RegExp("^/flow/stages/prompts$"), async () => { ],
[
"^/flow/stages/prompts$",
async () => {
await import("@goauthentik/admin/stages/prompt/PromptListPage"); await import("@goauthentik/admin/stages/prompt/PromptListPage");
return html`<ak-stage-prompt-list></ak-stage-prompt-list>`; return html`<ak-stage-prompt-list></ak-stage-prompt-list>`;
}), },
new Route(new RegExp("^/flow/stages$"), async () => { ],
[
"^/flow/stages$",
async () => {
await import("@goauthentik/admin/stages/StageListPage"); await import("@goauthentik/admin/stages/StageListPage");
return html`<ak-stage-list></ak-stage-list>`; return html`<ak-stage-list></ak-stage-list>`;
}), },
new Route(new RegExp("^/flow/flows$"), async () => { ],
[
"^/flow/flows$",
async () => {
await import("@goauthentik/admin/flows/FlowListPage"); await import("@goauthentik/admin/flows/FlowListPage");
return html`<ak-flow-list></ak-flow-list>`; return html`<ak-flow-list></ak-flow-list>`;
}), },
new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => { ],
[
`^/flow/flows/(?<slug>${SLUG_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/flows/FlowViewPage"); await import("@goauthentik/admin/flows/FlowViewPage");
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`; return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
}), },
new Route(new RegExp("^/events/log$"), async () => { ],
[
"^/events/log$",
async () => {
await import("@goauthentik/admin/events/EventListPage"); await import("@goauthentik/admin/events/EventListPage");
return html`<ak-event-list></ak-event-list>`; return html`<ak-event-list></ak-event-list>`;
}), },
new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => { ],
[
`^/events/log/(?<id>${UUID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/events/EventViewPage"); await import("@goauthentik/admin/events/EventViewPage");
return html`<ak-event-view .eventID=${args.id}></ak-event-view>`; return html`<ak-event-view .eventID=${args.id}></ak-event-view>`;
}), },
new Route(new RegExp("^/events/transports$"), async () => { ],
[
"^/events/transports$",
async () => {
await import("@goauthentik/admin/events/TransportListPage"); await import("@goauthentik/admin/events/TransportListPage");
return html`<ak-event-transport-list></ak-event-transport-list>`; return html`<ak-event-transport-list></ak-event-transport-list>`;
}), },
new Route(new RegExp("^/events/rules$"), async () => { ],
[
"^/events/rules$",
async () => {
await import("@goauthentik/admin/events/RuleListPage"); await import("@goauthentik/admin/events/RuleListPage");
return html`<ak-event-rule-list></ak-event-rule-list>`; return html`<ak-event-rule-list></ak-event-rule-list>`;
}), },
new Route(new RegExp("^/outpost/outposts$"), async () => { ],
[
"^/outpost/outposts$",
async () => {
await import("@goauthentik/admin/outposts/OutpostListPage"); await import("@goauthentik/admin/outposts/OutpostListPage");
return html`<ak-outpost-list></ak-outpost-list>`; return html`<ak-outpost-list></ak-outpost-list>`;
}), },
new Route(new RegExp("^/outpost/integrations$"), async () => { ],
[
"^/outpost/integrations$",
async () => {
await import("@goauthentik/admin/outposts/ServiceConnectionListPage"); await import("@goauthentik/admin/outposts/ServiceConnectionListPage");
return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`; return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`;
}), },
new Route(new RegExp("^/crypto/certificates$"), async () => { ],
[
"^/crypto/certificates$",
async () => {
await import("@goauthentik/admin/crypto/CertificateKeyPairListPage"); await import("@goauthentik/admin/crypto/CertificateKeyPairListPage");
return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`; return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
}), },
new Route(new RegExp("^/admin/settings$"), async () => { ],
[
"^/admin/settings$",
async () => {
await import("@goauthentik/admin/admin-settings/AdminSettingsPage"); await import("@goauthentik/admin/admin-settings/AdminSettingsPage");
return html`<ak-admin-settings></ak-admin-settings>`; return html`<ak-admin-settings></ak-admin-settings>`;
}), },
new Route(new RegExp("^/blueprints/instances$"), async () => { ],
[
"^/blueprints/instances$",
async () => {
await import("@goauthentik/admin/blueprints/BlueprintListPage"); await import("@goauthentik/admin/blueprints/BlueprintListPage");
return html`<ak-blueprint-list></ak-blueprint-list>`; return html`<ak-blueprint-list></ak-blueprint-list>`;
}), },
new Route(new RegExp("^/debug$"), async () => { ],
[
"^/debug$",
async () => {
await import("@goauthentik/admin/DebugPage"); await import("@goauthentik/admin/DebugPage");
return html`<ak-admin-debug-page></ak-admin-debug-page>`; return html`<ak-admin-debug-page></ak-admin-debug-page>`;
}), },
new Route(new RegExp("^/enterprise/licenses$"), async () => { ],
[
"^/enterprise/licenses$",
async () => {
await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage"); await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage");
return html`<ak-enterprise-license-list></ak-enterprise-license-list>`; return html`<ak-enterprise-license-list></ak-enterprise-license-list>`;
}), },
],
]; ];
export const ROUTES = _ROUTES.map(makeRoute);

View File

@ -31,17 +31,11 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
message: html`${msg(str`${value.versionLatest} is available!`)}`, message: html`${msg(str`${value.versionLatest} is available!`)}`,
}); });
} }
if (value.versionLatestValid) {
return Promise.resolve<AdminStatus>({ return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success", icon: "fa fa-check-circle pf-m-success",
message: html`${msg("Up-to-date!")}`, message: html`${msg("Up-to-date!")}`,
}); });
} }
return Promise.resolve<AdminStatus>({
icon: "fa fa-question-circle",
message: html`${msg("Latest version unknown")}`,
});
}
renderHeader(): TemplateResult { renderHeader(): TemplateResult {
return html`${msg("Version")}`; return html`${msg("Version")}`;

View File

@ -1,6 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-status-label"; import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form"; import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -84,7 +83,28 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
<div class="pf-c-form__group-label"> <div class="pf-c-form__group-label">
<div class="c-form__horizontal-group"> <div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal"> <dl class="pf-c-description-list pf-m-horizontal">
<ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer> ${(this.result?.logMessages || []).length > 0
? this.result?.logMessages?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("No log messages.")}</span
>
</dt>
</div>`}
</dl> </dl>
</div> </div>
</div> </div>

View File

@ -150,6 +150,10 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
renderSectionBefore(): TemplateResult { renderSectionBefore(): TemplateResult {
return html` return html`
<div class="pf-c-banner pf-m-info">
${msg("Enterprise is in preview.")}
<a href="mailto:hello@goauthentik.io">${msg("Send us feedback!")}</a>
</div>
<section class="pf-c-page__main-section pf-m-no-padding-bottom"> <section class="pf-c-page__main-section pf-m-no-padding-bottom">
<div <div
class="pf-l-grid pf-m-gutter pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-3-col-on-lg pf-m-all-3-col-on-xl" class="pf-l-grid pf-m-gutter pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-3-col-on-lg pf-m-all-3-col-on-xl"

View File

@ -23,15 +23,13 @@ export class BoundStagesList extends Table<FlowStageBinding> {
checkbox = true; checkbox = true;
clearOnRefresh = true; clearOnRefresh = true;
order = "order";
@property() @property()
target?: string; target?: string;
async apiEndpoint(page: number): Promise<PaginatedResponse<FlowStageBinding>> { async apiEndpoint(page: number): Promise<PaginatedResponse<FlowStageBinding>> {
return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({ return new FlowsApi(DEFAULT_CONFIG).flowsBindingsList({
target: this.target || "", target: this.target || "",
ordering: this.order, ordering: "order",
page: page, page: page,
pageSize: (await uiConfig()).pagination.perPage, pageSize: (await uiConfig()).pagination.perPage,
}); });
@ -39,8 +37,8 @@ export class BoundStagesList extends Table<FlowStageBinding> {
columns(): TableColumn[] { columns(): TableColumn[] {
return [ return [
new TableColumn(msg("Order"), "order"), new TableColumn(msg("Order")),
new TableColumn(msg("Name"), "stage__name"), new TableColumn(msg("Name")),
new TableColumn(msg("Type")), new TableColumn(msg("Type")),
new TableColumn(msg("Actions")), new TableColumn(msg("Actions")),
]; ];

View File

@ -1,7 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors"; import { SentryIgnoredError } from "@goauthentik/common/errors";
import "@goauthentik/components/ak-status-label"; import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form"; import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -56,7 +55,28 @@ export class FlowImportForm extends Form<Flow> {
<div class="pf-c-form__group-label"> <div class="pf-c-form__group-label">
<div class="c-form__horizontal-group"> <div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal"> <dl class="pf-c-description-list pf-m-horizontal">
<ak-log-viewer .logs=${this.result?.logs}></ak-log-viewer> ${(this.result?.logs || []).length > 0
? this.result?.logs?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("No log messages.")}</span
>
</dt>
</div>`}
</dl> </dl>
</div> </div>
</div> </div>

View File

@ -31,12 +31,10 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
checkbox = true; checkbox = true;
clearOnRefresh = true; clearOnRefresh = true;
order = "order";
async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> { async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({ return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
target: this.target || "", target: this.target || "",
ordering: this.order, ordering: "order",
page: page, page: page,
pageSize: (await uiConfig()).pagination.perPage, pageSize: (await uiConfig()).pagination.perPage,
}); });

View File

@ -3,7 +3,6 @@ import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-status-label"; import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form"; import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -86,7 +85,28 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
<div class="pf-c-form__group-label"> <div class="pf-c-form__group-label">
<div class="c-form__horizontal-group"> <div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal"> <dl class="pf-c-description-list pf-m-horizontal">
<ak-log-viewer .logs=${this.result?.logMessages}></ak-log-viewer> ${(this.result?.logMessages || []).length > 0
? this.result?.logMessages?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("No log messages.")}</span
>
</dt>
</div>`}
</dl> </dl>
</div> </div>
</div> </div>

View File

@ -175,7 +175,7 @@ export class OAuth2ProviderViewPage extends AKElement {
</div>`} </div>`}
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"> <div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
<div <div
class="pf-c-card pf-l-grid__item pf-l-grid__item pf-m-12-col pf-m-4-col-on-xl pf-m-4-col-on-2xl" class="pf-c-card pf-l-grid__item pf-l-grid__item pf-m-12-col pf-m-3-col-on-xl pf-m-3-col-on-2xl"
> >
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<dl class="pf-c-description-list"> <dl class="pf-c-description-list">
@ -255,7 +255,7 @@ export class OAuth2ProviderViewPage extends AKElement {
</ak-forms-modal> </ak-forms-modal>
</div> </div>
</div> </div>
<div class="pf-c-card pf-l-grid__item pf-m-8-col"> <div class="pf-c-card pf-l-grid__item pf-m-7-col">
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<form class="pf-c-form"> <form class="pf-c-form">
<div class="pf-c-form__group"> <div class="pf-c-form__group">

View File

@ -13,7 +13,7 @@ import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { ConnectionToken, RACProvider, RacApi } from "@goauthentik/api"; import { ConnectionToken, Endpoint, RACProvider, RacApi } from "@goauthentik/api";
@customElement("ak-rac-connection-token-list") @customElement("ak-rac-connection-token-list")
export class ConnectionTokenListPage extends Table<ConnectionToken> { export class ConnectionTokenListPage extends Table<ConnectionToken> {
@ -53,20 +53,20 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> {
return html`<ak-forms-delete-bulk return html`<ak-forms-delete-bulk
objectLabel=${msg("Connection Token(s)")} objectLabel=${msg("Connection Token(s)")}
.objects=${this.selectedElements} .objects=${this.selectedElements}
.metadata=${(item: ConnectionToken) => { .metadata=${(item: Endpoint) => {
return [ return [
{ key: msg("Endpoint"), value: item.endpointObj.name }, { key: msg("Name"), value: item.name },
{ key: msg("User"), value: item.user.username }, { key: msg("Host"), value: item.host },
]; ];
}} }}
.usedBy=${(item: ConnectionToken) => { .usedBy=${(item: Endpoint) => {
return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({ return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({
connectionTokenUuid: item.pk || "", connectionTokenUuid: item.pk,
}); });
}} }}
.delete=${(item: ConnectionToken) => { .delete=${(item: Endpoint) => {
return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({ return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({
connectionTokenUuid: item.pk || "", connectionTokenUuid: item.pk,
}); });
}} }}
> >

View File

@ -88,11 +88,7 @@ export class RACProviderViewPage extends AKElement {
<section slot="page-overview" data-tab-title="${msg("Overview")}"> <section slot="page-overview" data-tab-title="${msg("Overview")}">
${this.renderTabOverview()} ${this.renderTabOverview()}
</section> </section>
<section <section slot="page-connections" data-tab-title="${msg("Connections")}">
slot="page-connections"
data-tab-title="${msg("Connections")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__body"> <div class="pf-c-card__body">
<ak-rac-connection-token-list <ak-rac-connection-token-list

View File

@ -86,28 +86,6 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
<span class="pf-c-switch__label">${msg("Enabled")}</span> <span class="pf-c-switch__label">${msg("Enabled")}</span>
</label> </label>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal name="passwordLoginUpdateInternalPassword">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(this.instance?.passwordLoginUpdateInternalPassword, false)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"
>${msg("Update internal password on login")}</span
>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When the user logs in to authentik using this source password backend, update their credentials in authentik.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="syncUsers"> <ak-form-element-horizontal name="syncUsers">
<label class="pf-c-switch"> <label class="pf-c-switch">
<input <input

View File

@ -15,7 +15,6 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm"; import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm"; import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm"; import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; import "@goauthentik/admin/stages/user_login/UserLoginStageForm";
import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm"; import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm";

View File

@ -1,4 +1,3 @@
import "@goauthentik/admin/common/ak-license-notice";
import { StageBindingForm } from "@goauthentik/admin/flows/StageBindingForm"; import { StageBindingForm } from "@goauthentik/admin/flows/StageBindingForm";
import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm"; import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm";
import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm"; import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
@ -15,14 +14,12 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm"; import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm"; import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm"; import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm"; import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm"; import "@goauthentik/admin/stages/user_login/UserLoginStageForm";
import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm"; import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm";
import "@goauthentik/admin/stages/user_write/UserWriteStageForm"; import "@goauthentik/admin/stages/user_write/UserWriteStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/forms/ProxyForm";
import "@goauthentik/elements/wizard/FormWizardPage"; import "@goauthentik/elements/wizard/FormWizardPage";
import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage"; import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage";
@ -31,7 +28,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html, nothing } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { property } from "lit/decorators.js"; import { property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -42,7 +39,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { FlowStageBinding, Stage, StagesApi, TypeCreate } from "@goauthentik/api"; import { FlowStageBinding, Stage, StagesApi, TypeCreate } from "@goauthentik/api";
@customElement("ak-stage-wizard-initial") @customElement("ak-stage-wizard-initial")
export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) { export class InitialStageWizardPage extends WizardPage {
@property({ attribute: false }) @property({ attribute: false })
stageTypes: TypeCreate[] = []; stageTypes: TypeCreate[] = [];
sidebarLabel = () => msg("Select type"); sidebarLabel = () => msg("Select type");
@ -65,7 +62,6 @@ export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) {
render(): TemplateResult { render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal"> return html`<form class="pf-c-form pf-m-horizontal">
${this.stageTypes.map((type) => { ${this.stageTypes.map((type) => {
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
return html`<div class="pf-c-radio"> return html`<div class="pf-c-radio">
<input <input
class="pf-c-radio__input" class="pf-c-radio__input"
@ -86,15 +82,11 @@ export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) {
); );
this.host.isValid = true; this.host.isValid = true;
}} }}
?disabled=${requiresEnterprise}
/> />
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`} <label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
>${type.name}</label >${type.name}</label
> >
<span class="pf-c-radio__description">${type.description}${ <span class="pf-c-radio__description">${type.description}</span>
requiresEnterprise ? html`<ak-license-notice></ak-license-notice>` : nothing
}</span>
</span>
</div>`; </div>`;
})} })}
</form>`; </form>`;

View File

@ -1,99 +0,0 @@
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect/index";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
Source,
SourceStage,
SourcesAllListRequest,
SourcesApi,
StagesApi,
} from "@goauthentik/api";
@customElement("ak-stage-source-form")
export class SourceStageForm extends BaseStageForm<SourceStage> {
loadInstance(pk: string): Promise<SourceStage> {
return new StagesApi(DEFAULT_CONFIG).stagesSourceRetrieve({
stageUuid: pk,
});
}
async send(data: SourceStage): Promise<SourceStage> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesSourceUpdate({
stageUuid: this.instance.pk || "",
sourceStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesSourceCreate({
sourceStageRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`
<span> ${msg("TODO.")} </span>
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Source")} ?required=${true} name="source">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Source[]> => {
const args: SourcesAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const users = await new SourcesApi(DEFAULT_CONFIG).sourcesAllList(args);
return users.results;
}}
.renderElement=${(source: Source): string => {
return source.name;
}}
.renderDescription=${(source: Source): TemplateResult => {
return html`${source.verboseName}`;
}}
.value=${(source: Source | undefined): string | undefined => {
return source?.pk;
}}
.selected=${(source: Source): boolean => {
return source.pk === this.instance?.source;
}}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Resume timeout")}
?required=${true}
name="resumeTimeout"
>
<input
type="text"
value="${ifDefined(this.instance?.resumeTimeout || "minutes=10")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"Amount of time a user can take to return from the source to continue the flow.",
)}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
`;
}
}

Some files were not shown because too many files have changed in this diff Show More