Compare commits
33 Commits
core/make-
...
blueprint-
Author | SHA1 | Date | |
---|---|---|---|
563c274d70 | |||
a9fee67b44 | |||
28d8fcc115 | |||
c436205e3d | |||
3f788e7abe | |||
b7a1a9c107 | |||
559ec290d0 | |||
05279514f8 | |||
061275d243 | |||
30e7f7acbd | |||
80ab39675c | |||
e63f13c9fe | |||
08b07aebb9 | |||
83219ff2ca | |||
be3e01912d | |||
0e180ebd21 | |||
190cb33f8e | |||
15061dab6d | |||
d5a7f0fc3a | |||
c1525449cf | |||
f23965a55e | |||
de5191be6c | |||
0bc2d4a7b8 | |||
4b5e66f9eb | |||
9a5effae2c | |||
ff504a3b80 | |||
587f2d74ac | |||
c3555c778c | |||
1acf48ae1e | |||
a32d396cec | |||
deacc17832 | |||
96b3e2b3d9 | |||
ddd3b0557e |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@ -35,7 +35,7 @@ runs:
|
||||
run: |
|
||||
export PSQL_TAG=${{ inputs.postgresql_version }}
|
||||
docker compose -f .github/actions/setup/docker-compose.yml up -d
|
||||
poetry install
|
||||
poetry install --sync
|
||||
cd web && npm ci
|
||||
- name: Generate config
|
||||
shell: poetry run python {0}
|
||||
|
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -134,7 +134,7 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1.10.0
|
||||
uses: helm/kind-action@v1.11.0
|
||||
- name: run integration
|
||||
run: |
|
||||
poetry run coverage run manage.py test tests/integration
|
||||
|
@ -19,10 +19,15 @@ Dockerfile @goauthentik/infrastructure
|
||||
*Dockerfile @goauthentik/infrastructure
|
||||
.dockerignore @goauthentik/infrastructure
|
||||
docker-compose.yml @goauthentik/infrastructure
|
||||
Makefile @goauthentik/infrastructure
|
||||
.editorconfig @goauthentik/infrastructure
|
||||
CODEOWNERS @goauthentik/infrastructure
|
||||
# Web
|
||||
web/ @goauthentik/frontend
|
||||
tests/wdio/ @goauthentik/frontend
|
||||
# Docs & Website
|
||||
website/ @goauthentik/docs
|
||||
CODE_OF_CONDUCT.md @goauthentik/docs
|
||||
# Security
|
||||
website/docs/security/ @goauthentik/security
|
||||
SECURITY.md @goauthentik/security @goauthentik/docs
|
||||
website/docs/security/ @goauthentik/security @goauthentik/docs
|
||||
|
@ -2,7 +2,7 @@ authentik takes security very seriously. We follow the rules of [responsible di
|
||||
|
||||
## Independent audits and pentests
|
||||
|
||||
We are committed to engaging in regular pentesting and security audits of authentik. Defining and adhering to a cadence of external testing ensures a stronger probability that our code base, our features, and our architecture is as secure and non-exploitable as possible. For more details about specfic audits and pentests, refer to "Audits and Certificates" in our [Security documentation]](https://docs.goauthentik.io/docs/security).
|
||||
We are committed to engaging in regular pentesting and security audits of authentik. Defining and adhering to a cadence of external testing ensures a stronger probability that our code base, our features, and our architecture is as secure and non-exploitable as possible. For more details about specfic audits and pentests, refer to "Audits and Certificates" in our [Security documentation](https://docs.goauthentik.io/docs/security).
|
||||
|
||||
## What authentik classifies as a CVE
|
||||
|
||||
|
@ -5,8 +5,9 @@ from hashlib import sha512
|
||||
from pathlib import Path
|
||||
from sys import platform
|
||||
|
||||
import pglock
|
||||
from dacite.core import from_dict
|
||||
from django.db import DatabaseError, InternalError, ProgrammingError
|
||||
from django.db import DatabaseError, InternalError, ProgrammingError, connection
|
||||
from django.utils.text import slugify
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -152,15 +153,27 @@ def blueprints_find() -> list[BlueprintFile]:
|
||||
@prefill_task
|
||||
def blueprints_discovery(self: SystemTask, path: str | None = None):
|
||||
"""Find blueprints and check if they need to be created in the database"""
|
||||
count = 0
|
||||
for blueprint in blueprints_find():
|
||||
if path and blueprint.path != path:
|
||||
continue
|
||||
check_blueprint_v1_file(blueprint)
|
||||
count += 1
|
||||
self.set_status(
|
||||
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=count))
|
||||
)
|
||||
with pglock.advisory(
|
||||
lock_id=f"goauthentik.io/{connection.schema_name}/blueprints/discovery",
|
||||
timeout=0,
|
||||
side_effect=pglock.Return,
|
||||
) as lock_acquired:
|
||||
if not lock_acquired:
|
||||
LOGGER.debug("Not running blueprint discovery, lock was not acquired")
|
||||
self.set_status(
|
||||
TaskStatus.SUCCESSFUL,
|
||||
_("Blueprint discovery lock could not be acquired. Skipping discovery."),
|
||||
)
|
||||
return
|
||||
count = 0
|
||||
for blueprint in blueprints_find():
|
||||
if path and blueprint.path != path:
|
||||
continue
|
||||
check_blueprint_v1_file(blueprint)
|
||||
count += 1
|
||||
self.set_status(
|
||||
TaskStatus.SUCCESSFUL, _("Successfully imported {count} files.".format(count=count))
|
||||
)
|
||||
|
||||
|
||||
def check_blueprint_v1_file(blueprint: BlueprintFile):
|
||||
@ -197,48 +210,60 @@ def check_blueprint_v1_file(blueprint: BlueprintFile):
|
||||
def apply_blueprint(self: SystemTask, instance_pk: str):
|
||||
"""Apply single blueprint"""
|
||||
self.save_on_success = False
|
||||
instance: BlueprintInstance | None = None
|
||||
try:
|
||||
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
|
||||
if not instance or not instance.enabled:
|
||||
with pglock.advisory(
|
||||
lock_id=f"goauthentik.io/{connection.schema_name}/blueprints/apply/{instance_pk}",
|
||||
timeout=0,
|
||||
side_effect=pglock.Return,
|
||||
) as lock_acquired:
|
||||
if not lock_acquired:
|
||||
LOGGER.debug("Not running blueprint discovery, lock was not acquired")
|
||||
self.set_status(
|
||||
TaskStatus.SUCCESSFUL,
|
||||
_("Blueprint apply lock could not be acquired. Skipping apply."),
|
||||
)
|
||||
return
|
||||
self.set_uid(slugify(instance.name))
|
||||
blueprint_content = instance.retrieve()
|
||||
file_hash = sha512(blueprint_content.encode()).hexdigest()
|
||||
importer = Importer.from_string(blueprint_content, instance.context)
|
||||
if importer.blueprint.metadata:
|
||||
instance.metadata = asdict(importer.blueprint.metadata)
|
||||
valid, logs = importer.validate()
|
||||
if not valid:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
instance.save()
|
||||
self.set_status(TaskStatus.ERROR, *logs)
|
||||
return
|
||||
with capture_logs() as logs:
|
||||
applied = importer.apply()
|
||||
if not applied:
|
||||
instance: BlueprintInstance | None = None
|
||||
try:
|
||||
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
|
||||
if not instance or not instance.enabled:
|
||||
return
|
||||
self.set_uid(slugify(instance.name))
|
||||
blueprint_content = instance.retrieve()
|
||||
file_hash = sha512(blueprint_content.encode()).hexdigest()
|
||||
importer = Importer.from_string(blueprint_content, instance.context)
|
||||
if importer.blueprint.metadata:
|
||||
instance.metadata = asdict(importer.blueprint.metadata)
|
||||
valid, logs = importer.validate()
|
||||
if not valid:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
instance.save()
|
||||
self.set_status(TaskStatus.ERROR, *logs)
|
||||
return
|
||||
instance.status = BlueprintInstanceStatus.SUCCESSFUL
|
||||
instance.last_applied_hash = file_hash
|
||||
instance.last_applied = now()
|
||||
self.set_status(TaskStatus.SUCCESSFUL)
|
||||
except (
|
||||
OSError,
|
||||
DatabaseError,
|
||||
ProgrammingError,
|
||||
InternalError,
|
||||
BlueprintRetrievalFailed,
|
||||
EntryInvalidError,
|
||||
) as exc:
|
||||
if instance:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
self.set_error(exc)
|
||||
finally:
|
||||
if instance:
|
||||
instance.save()
|
||||
with capture_logs() as logs:
|
||||
applied = importer.apply()
|
||||
if not applied:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
instance.save()
|
||||
self.set_status(TaskStatus.ERROR, *logs)
|
||||
return
|
||||
instance.status = BlueprintInstanceStatus.SUCCESSFUL
|
||||
instance.last_applied_hash = file_hash
|
||||
instance.last_applied = now()
|
||||
self.set_status(TaskStatus.SUCCESSFUL)
|
||||
except (
|
||||
OSError,
|
||||
DatabaseError,
|
||||
ProgrammingError,
|
||||
InternalError,
|
||||
BlueprintRetrievalFailed,
|
||||
EntryInvalidError,
|
||||
) as exc:
|
||||
if instance:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
self.set_error(exc)
|
||||
finally:
|
||||
if instance:
|
||||
instance.save()
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
|
@ -40,6 +40,7 @@ class Migration(migrations.Migration):
|
||||
("require_authenticated", "Require Authenticated"),
|
||||
("require_unauthenticated", "Require Unauthenticated"),
|
||||
("require_superuser", "Require Superuser"),
|
||||
("require_redirect", "Require Redirect"),
|
||||
("require_outpost", "Require Outpost"),
|
||||
],
|
||||
default="none",
|
||||
|
@ -33,6 +33,7 @@ class FlowAuthenticationRequirement(models.TextChoices):
|
||||
REQUIRE_AUTHENTICATED = "require_authenticated"
|
||||
REQUIRE_UNAUTHENTICATED = "require_unauthenticated"
|
||||
REQUIRE_SUPERUSER = "require_superuser"
|
||||
REQUIRE_REDIRECT = "require_redirect"
|
||||
REQUIRE_OUTPOST = "require_outpost"
|
||||
|
||||
|
||||
|
@ -42,6 +42,8 @@ PLAN_CONTEXT_OUTPOST = "outpost"
|
||||
# Is set by the Flow Planner when a FlowToken was used, and the currently active flow plan
|
||||
# was restored.
|
||||
PLAN_CONTEXT_IS_RESTORED = "is_restored"
|
||||
PLAN_CONTEXT_IS_REDIRECTED = "is_redirected"
|
||||
PLAN_CONTEXT_REDIRECT_STAGE_TARGET = "redirect_stage_target"
|
||||
CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_flows")
|
||||
CACHE_PREFIX = "goauthentik.io/flows/planner/"
|
||||
|
||||
@ -181,7 +183,7 @@ class FlowPlanner:
|
||||
self.flow = flow
|
||||
self._logger = get_logger().bind(flow_slug=flow.slug)
|
||||
|
||||
def _check_authentication(self, request: HttpRequest):
|
||||
def _check_authentication(self, request: HttpRequest, context: dict[str, Any]):
|
||||
"""Check the flow's authentication level is matched by `request`"""
|
||||
if (
|
||||
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
|
||||
@ -198,6 +200,11 @@ class FlowPlanner:
|
||||
and not request.user.is_superuser
|
||||
):
|
||||
raise FlowNonApplicableException()
|
||||
if (
|
||||
self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_REDIRECT
|
||||
and context.get(PLAN_CONTEXT_IS_REDIRECTED) is None
|
||||
):
|
||||
raise FlowNonApplicableException()
|
||||
outpost_user = ClientIPMiddleware.get_outpost_user(request)
|
||||
if self.flow.authentication == FlowAuthenticationRequirement.REQUIRE_OUTPOST:
|
||||
if not outpost_user:
|
||||
@ -229,18 +236,13 @@ class FlowPlanner:
|
||||
)
|
||||
context = default_context or {}
|
||||
# Bit of a workaround here, if there is a pending user set in the default context
|
||||
# we use that user for our cache key
|
||||
# to make sure they don't get the generic response
|
||||
# we use that user for our cache key to make sure they don't get the generic response
|
||||
if context and PLAN_CONTEXT_PENDING_USER in context:
|
||||
user = context[PLAN_CONTEXT_PENDING_USER]
|
||||
else:
|
||||
user = request.user
|
||||
# We only need to check the flow authentication if it's planned without a user
|
||||
# in the context, as a user in the context can only be set via the explicit code API
|
||||
# or if a flow is restarted due to `invalid_response_action` being set to
|
||||
# `restart_with_context`, which can only happen if the user was already authorized
|
||||
# to use the flow
|
||||
context.update(self._check_authentication(request))
|
||||
|
||||
context.update(self._check_authentication(request, context))
|
||||
# First off, check the flow's direct policy bindings
|
||||
# to make sure the user even has access to the flow
|
||||
engine = PolicyEngine(self.flow, user, request)
|
||||
|
@ -93,7 +93,11 @@ class ChallengeStageView(StageView):
|
||||
|
||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
"""Return a challenge for the frontend to solve"""
|
||||
challenge = self._get_challenge(*args, **kwargs)
|
||||
try:
|
||||
challenge = self._get_challenge(*args, **kwargs)
|
||||
except StageInvalidException as exc:
|
||||
self.logger.debug("Got StageInvalidException", exc=exc)
|
||||
return self.executor.stage_invalid()
|
||||
if not challenge.is_valid():
|
||||
self.logger.warning(
|
||||
"f(ch): Invalid challenge",
|
||||
@ -169,11 +173,7 @@ class ChallengeStageView(StageView):
|
||||
stage_type=self.__class__.__name__, method="get_challenge"
|
||||
).time(),
|
||||
):
|
||||
try:
|
||||
challenge = self.get_challenge(*args, **kwargs)
|
||||
except StageInvalidException as exc:
|
||||
self.logger.debug("Got StageInvalidException", exc=exc)
|
||||
return self.executor.stage_invalid()
|
||||
challenge = self.get_challenge(*args, **kwargs)
|
||||
with start_span(
|
||||
op="authentik.flow.stage._get_challenge",
|
||||
name=self.__class__.__name__,
|
||||
|
@ -22,7 +22,12 @@ from authentik.flows.models import (
|
||||
FlowStageBinding,
|
||||
in_memory_stage,
|
||||
)
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
|
||||
from authentik.flows.planner import (
|
||||
PLAN_CONTEXT_IS_REDIRECTED,
|
||||
PLAN_CONTEXT_PENDING_USER,
|
||||
FlowPlanner,
|
||||
cache_key,
|
||||
)
|
||||
from authentik.flows.stage import StageView
|
||||
from authentik.lib.tests.utils import dummy_get_response
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
@ -81,6 +86,24 @@ class TestFlowPlanner(TestCase):
|
||||
planner.allow_empty_flows = True
|
||||
planner.plan(request)
|
||||
|
||||
def test_authentication_redirect_required(self):
|
||||
"""Test flow authentication (redirect required)"""
|
||||
flow = create_test_flow()
|
||||
flow.authentication = FlowAuthenticationRequirement.REQUIRE_REDIRECT
|
||||
request = self.request_factory.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||
)
|
||||
request.user = AnonymousUser()
|
||||
planner = FlowPlanner(flow)
|
||||
planner.allow_empty_flows = True
|
||||
|
||||
with self.assertRaises(FlowNonApplicableException):
|
||||
planner.plan(request)
|
||||
|
||||
context = {}
|
||||
context[PLAN_CONTEXT_IS_REDIRECTED] = create_test_flow()
|
||||
planner.plan(request, context)
|
||||
|
||||
@reconcile_app("authentik_outposts")
|
||||
def test_authentication_outpost(self):
|
||||
"""Test flow authentication (outpost)"""
|
||||
|
@ -171,7 +171,8 @@ class FlowExecutorView(APIView):
|
||||
# Existing plan is deleted from session and instance
|
||||
self.plan = None
|
||||
self.cancel()
|
||||
self._logger.debug("f(exec): Continuing existing plan")
|
||||
else:
|
||||
self._logger.debug("f(exec): Continuing existing plan")
|
||||
|
||||
# Initial flow request, check if we have an upstream query string passed in
|
||||
request.session[SESSION_KEY_GET] = get_params
|
||||
|
@ -5,6 +5,7 @@ import json
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
from contextlib import contextmanager
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from glob import glob
|
||||
@ -336,6 +337,58 @@ def redis_url(db: int) -> str:
|
||||
return _redis_url
|
||||
|
||||
|
||||
def django_db_config(config: ConfigLoader | None = None) -> dict:
|
||||
if not config:
|
||||
config = CONFIG
|
||||
db = {
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": config.get("postgresql.host"),
|
||||
"NAME": config.get("postgresql.name"),
|
||||
"USER": config.get("postgresql.user"),
|
||||
"PASSWORD": config.get("postgresql.password"),
|
||||
"PORT": config.get("postgresql.port"),
|
||||
"OPTIONS": {
|
||||
"sslmode": config.get("postgresql.sslmode"),
|
||||
"sslrootcert": config.get("postgresql.sslrootcert"),
|
||||
"sslcert": config.get("postgresql.sslcert"),
|
||||
"sslkey": config.get("postgresql.sslkey"),
|
||||
},
|
||||
"TEST": {
|
||||
"NAME": config.get("postgresql.test.name"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if config.get_bool("postgresql.use_pgpool", False):
|
||||
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||
|
||||
if config.get_bool("postgresql.use_pgbouncer", False):
|
||||
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
|
||||
db["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
|
||||
db["default"]["CONN_MAX_AGE"] = None # persistent
|
||||
|
||||
for replica in config.get_keys("postgresql.read_replicas"):
|
||||
_database = deepcopy(db["default"])
|
||||
for setting, current_value in db["default"].items():
|
||||
if isinstance(current_value, dict):
|
||||
continue
|
||||
override = config.get(
|
||||
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
|
||||
)
|
||||
if override is not UNSET:
|
||||
_database[setting] = override
|
||||
for setting in db["default"]["OPTIONS"].keys():
|
||||
override = config.get(
|
||||
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=UNSET
|
||||
)
|
||||
if override is not UNSET:
|
||||
_database["OPTIONS"][setting] = override
|
||||
db[f"replica_{replica}"] = _database
|
||||
return db
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(argv) < 2: # noqa: PLR2004
|
||||
print(dumps(CONFIG.raw, indent=4, cls=AttrEncoder))
|
||||
|
@ -9,7 +9,14 @@ from unittest import mock
|
||||
from django.conf import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.lib.config import ENV_PREFIX, UNSET, Attr, AttrEncoder, ConfigLoader
|
||||
from authentik.lib.config import (
|
||||
ENV_PREFIX,
|
||||
UNSET,
|
||||
Attr,
|
||||
AttrEncoder,
|
||||
ConfigLoader,
|
||||
django_db_config,
|
||||
)
|
||||
|
||||
|
||||
class TestConfig(TestCase):
|
||||
@ -175,3 +182,201 @@ class TestConfig(TestCase):
|
||||
config = ConfigLoader()
|
||||
config.set("foo.bar", "baz")
|
||||
self.assertEqual(list(config.get_keys("foo")), ["bar"])
|
||||
|
||||
def test_db_default(self):
|
||||
"""Test default DB Config"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.sslmode", "foo")
|
||||
config.set("postgresql.sslrootcert", "foo")
|
||||
config.set("postgresql.sslcert", "foo")
|
||||
config.set("postgresql.sslkey", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "foo",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_db_read_replicas(self):
|
||||
"""Test read replicas"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.sslmode", "foo")
|
||||
config.set("postgresql.sslrootcert", "foo")
|
||||
config.set("postgresql.sslcert", "foo")
|
||||
config.set("postgresql.sslkey", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
# Read replica
|
||||
config.set("postgresql.read_replicas.0.host", "bar")
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "foo",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
},
|
||||
"replica_0": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "bar",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "foo",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_db_read_replicas_pgpool(self):
|
||||
"""Test read replicas"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.sslmode", "foo")
|
||||
config.set("postgresql.sslrootcert", "foo")
|
||||
config.set("postgresql.sslcert", "foo")
|
||||
config.set("postgresql.sslkey", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
config.set("postgresql.use_pgpool", True)
|
||||
# Read replica
|
||||
config.set("postgresql.read_replicas.0.host", "bar")
|
||||
# This isn't supported
|
||||
config.set("postgresql.read_replicas.0.use_pgpool", False)
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"DISABLE_SERVER_SIDE_CURSORS": True,
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "foo",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
},
|
||||
"replica_0": {
|
||||
"DISABLE_SERVER_SIDE_CURSORS": True,
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "bar",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "foo",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_db_read_replicas_diff_ssl(self):
|
||||
"""Test read replicas (with different SSL Settings)"""
|
||||
"""Test read replicas"""
|
||||
config = ConfigLoader()
|
||||
config.set("postgresql.host", "foo")
|
||||
config.set("postgresql.name", "foo")
|
||||
config.set("postgresql.user", "foo")
|
||||
config.set("postgresql.password", "foo")
|
||||
config.set("postgresql.port", "foo")
|
||||
config.set("postgresql.sslmode", "foo")
|
||||
config.set("postgresql.sslrootcert", "foo")
|
||||
config.set("postgresql.sslcert", "foo")
|
||||
config.set("postgresql.sslkey", "foo")
|
||||
config.set("postgresql.test.name", "foo")
|
||||
# Read replica
|
||||
config.set("postgresql.read_replicas.0.host", "bar")
|
||||
config.set("postgresql.read_replicas.0.sslcert", "bar")
|
||||
conf = django_db_config(config)
|
||||
self.assertEqual(
|
||||
conf,
|
||||
{
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "foo",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "foo",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
},
|
||||
"replica_0": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": "bar",
|
||||
"NAME": "foo",
|
||||
"OPTIONS": {
|
||||
"sslcert": "bar",
|
||||
"sslkey": "foo",
|
||||
"sslmode": "foo",
|
||||
"sslrootcert": "foo",
|
||||
},
|
||||
"PASSWORD": "foo",
|
||||
"PORT": "foo",
|
||||
"TEST": {"NAME": "foo"},
|
||||
"USER": "foo",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.0.10 on 2024-12-12 17:16
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0040_provider_invalidation_flow"),
|
||||
(
|
||||
"authentik_providers_oauth2",
|
||||
"0025_rename_jwks_sources_oauth2provider_jwt_federation_sources_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="accesstoken",
|
||||
name="session",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="authentik_core.authenticatedsession",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="authorizationcode",
|
||||
name="session",
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="authentik_core.authenticatedsession",
|
||||
),
|
||||
),
|
||||
]
|
@ -396,7 +396,7 @@ class BaseGrantModel(models.Model):
|
||||
_scope = models.TextField(default="", verbose_name=_("Scopes"))
|
||||
auth_time = models.DateTimeField(verbose_name="Authentication time")
|
||||
session = models.ForeignKey(
|
||||
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
|
||||
AuthenticatedSession, null=True, on_delete=models.CASCADE, default=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -497,6 +497,11 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
|
||||
token = models.TextField(default=generate_client_secret)
|
||||
_id_token = models.TextField(verbose_name=_("ID Token"))
|
||||
# Shadow the `session` field from `BaseGrantModel` as we want refresh tokens to persist even
|
||||
# when the session is terminated.
|
||||
session = models.ForeignKey(
|
||||
AuthenticatedSession, null=True, on_delete=models.SET_DEFAULT, default=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
|
@ -12,7 +12,7 @@ from sentry_sdk import set_tag
|
||||
from xmlsec import enable_debug_trace
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.lib.config import CONFIG, redis_url
|
||||
from authentik.lib.config import CONFIG, django_db_config, redis_url
|
||||
from authentik.lib.logging import get_logger_config, structlog_configure
|
||||
from authentik.lib.sentry import sentry_init
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
@ -114,6 +114,7 @@ TENANT_APPS = [
|
||||
"authentik.stages.invitation",
|
||||
"authentik.stages.password",
|
||||
"authentik.stages.prompt",
|
||||
"authentik.stages.redirect",
|
||||
"authentik.stages.user_delete",
|
||||
"authentik.stages.user_login",
|
||||
"authentik.stages.user_logout",
|
||||
@ -297,47 +298,7 @@ CHANNEL_LAYERS = {
|
||||
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
||||
|
||||
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "authentik.root.db",
|
||||
"HOST": CONFIG.get("postgresql.host"),
|
||||
"NAME": CONFIG.get("postgresql.name"),
|
||||
"USER": CONFIG.get("postgresql.user"),
|
||||
"PASSWORD": CONFIG.get("postgresql.password"),
|
||||
"PORT": CONFIG.get("postgresql.port"),
|
||||
"OPTIONS": {
|
||||
"sslmode": CONFIG.get("postgresql.sslmode"),
|
||||
"sslrootcert": CONFIG.get("postgresql.sslrootcert"),
|
||||
"sslcert": CONFIG.get("postgresql.sslcert"),
|
||||
"sslkey": CONFIG.get("postgresql.sslkey"),
|
||||
},
|
||||
"TEST": {
|
||||
"NAME": CONFIG.get("postgresql.test.name"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if CONFIG.get_bool("postgresql.use_pgpool", False):
|
||||
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||
|
||||
if CONFIG.get_bool("postgresql.use_pgbouncer", False):
|
||||
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
|
||||
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
||||
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
|
||||
DATABASES["default"]["CONN_MAX_AGE"] = None # persistent
|
||||
|
||||
for replica in CONFIG.get_keys("postgresql.read_replicas"):
|
||||
_database = DATABASES["default"].copy()
|
||||
for setting in DATABASES["default"].keys():
|
||||
default = object()
|
||||
if setting in ("TEST",):
|
||||
continue
|
||||
override = CONFIG.get(
|
||||
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=default
|
||||
)
|
||||
if override is not default:
|
||||
_database[setting] = override
|
||||
DATABASES[f"replica_{replica}"] = _database
|
||||
DATABASES = django_db_config()
|
||||
|
||||
DATABASE_ROUTERS = (
|
||||
"authentik.tenants.db.FailoverRouter",
|
||||
|
@ -32,6 +32,7 @@ class KerberosSourceSerializer(SourceSerializer):
|
||||
"group_matching_mode",
|
||||
"realm",
|
||||
"krb5_conf",
|
||||
"kadmin_type",
|
||||
"sync_users",
|
||||
"sync_users_password",
|
||||
"sync_principal",
|
||||
@ -69,6 +70,7 @@ class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
"slug",
|
||||
"enabled",
|
||||
"realm",
|
||||
"kadmin_type",
|
||||
"sync_users",
|
||||
"sync_users_password",
|
||||
"sync_principal",
|
||||
|
@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.0.10 on 2024-12-06 19:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_kerberos", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="kerberossource",
|
||||
name="kadmin_type",
|
||||
field=models.TextField(
|
||||
choices=[("MIT", "Mit"), ("Heimdal", "Heimdal"), ("other", "Other")],
|
||||
default="other",
|
||||
help_text="KAdmin server type",
|
||||
),
|
||||
),
|
||||
]
|
@ -13,7 +13,7 @@ from django.http import HttpRequest
|
||||
from django.shortcuts import reverse
|
||||
from django.templatetags.static import static
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from kadmin import KAdmin
|
||||
from kadmin import KAdmin, KAdminApiVersion
|
||||
from kadmin.exceptions import PyKAdminException
|
||||
from rest_framework.serializers import Serializer
|
||||
from structlog.stdlib import get_logger
|
||||
@ -36,6 +36,12 @@ LOGGER = get_logger()
|
||||
_kadmin_connections: dict[str, Any] = {}
|
||||
|
||||
|
||||
class KAdminType(models.TextChoices):
|
||||
MIT = "MIT"
|
||||
HEIMDAL = "Heimdal"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class KerberosSource(Source):
|
||||
"""Federate Kerberos realm with authentik"""
|
||||
|
||||
@ -44,6 +50,9 @@ class KerberosSource(Source):
|
||||
blank=True,
|
||||
help_text=_("Custom krb5.conf to use. Uses the system one by default"),
|
||||
)
|
||||
kadmin_type = models.TextField(
|
||||
choices=KAdminType.choices, default=KAdminType.OTHER, help_text=_("KAdmin server type")
|
||||
)
|
||||
|
||||
sync_users = models.BooleanField(
|
||||
default=False, help_text=_("Sync users from Kerberos into authentik"), db_index=True
|
||||
@ -199,6 +208,14 @@ class KerberosSource(Source):
|
||||
return str(conf_path)
|
||||
|
||||
def _kadmin_init(self) -> KAdmin | None:
|
||||
api_version = None
|
||||
match self.kadmin_type:
|
||||
case KAdminType.MIT:
|
||||
api_version = KAdminApiVersion.Version4
|
||||
case KAdminType.HEIMDAL:
|
||||
api_version = KAdminApiVersion.Version2
|
||||
case KAdminType.OTHER:
|
||||
api_version = KAdminApiVersion.Version2
|
||||
# kadmin doesn't use a ccache for its connection
|
||||
# as such, we don't need to create a separate ccache for each source
|
||||
if not self.sync_principal:
|
||||
@ -207,6 +224,7 @@ class KerberosSource(Source):
|
||||
return KAdmin.with_password(
|
||||
self.sync_principal,
|
||||
self.sync_password,
|
||||
api_version=api_version,
|
||||
)
|
||||
if self.sync_keytab:
|
||||
keytab = self.sync_keytab
|
||||
@ -218,11 +236,13 @@ class KerberosSource(Source):
|
||||
return KAdmin.with_keytab(
|
||||
self.sync_principal,
|
||||
keytab,
|
||||
api_version=api_version,
|
||||
)
|
||||
if self.sync_ccache:
|
||||
return KAdmin.with_ccache(
|
||||
self.sync_principal,
|
||||
self.sync_ccache,
|
||||
api_version=api_version,
|
||||
)
|
||||
return None
|
||||
|
||||
|
@ -43,8 +43,10 @@ class KerberosSync:
|
||||
self._messages = []
|
||||
self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
|
||||
self.mapper = SourceMapper(self._source)
|
||||
self.user_manager = self.mapper.get_manager(User, ["principal"])
|
||||
self.group_manager = self.mapper.get_manager(Group, ["group_id", "principal"])
|
||||
self.user_manager = self.mapper.get_manager(User, ["principal", "principal_obj"])
|
||||
self.group_manager = self.mapper.get_manager(
|
||||
Group, ["group_id", "principal", "principal_obj"]
|
||||
)
|
||||
self.matcher = SourceMatcher(
|
||||
self._source, UserKerberosSourceConnection, GroupKerberosSourceConnection
|
||||
)
|
||||
@ -67,12 +69,16 @@ class KerberosSync:
|
||||
|
||||
def _handle_principal(self, principal: str) -> bool:
|
||||
try:
|
||||
# TODO: handle permission error
|
||||
principal_obj = self._connection.get_principal(principal)
|
||||
|
||||
defaults = self.mapper.build_object_properties(
|
||||
object_type=User,
|
||||
manager=self.user_manager,
|
||||
user=None,
|
||||
request=None,
|
||||
principal=principal,
|
||||
principal_obj=principal_obj,
|
||||
)
|
||||
self._logger.debug("Writing user with attributes", **defaults)
|
||||
if "username" not in defaults:
|
||||
@ -91,6 +97,7 @@ class KerberosSync:
|
||||
request=None,
|
||||
group_id=group_id,
|
||||
principal=principal,
|
||||
principal_obj=principal_obj,
|
||||
)
|
||||
for group_id in defaults.pop("groups", [])
|
||||
}
|
||||
|
@ -141,5 +141,10 @@
|
||||
"name": "Devolutions",
|
||||
"icon_dark": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg==",
|
||||
"icon_light": "data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNzJweCIgaGVpZ2h0PSI3MnB4IiB2aWV3Qm94PSIwIDAgNzIgNzIiPgk8ZGVmcz4KICAgICAgICA8ZmlsdGVyIGlkPSJhIiB3aWR0aD0iMjAwJSIgaGVpZ2h0PSIyMDAlIj4KICAgICAgICAgICAgPGZlT2Zmc2V0IHJlc3VsdD0ib2ZmT3V0IiBpbj0iU291cmNlQWxwaGEiIGR5PSIyLjIiLz4KICAgICAgICAgICAgPGZlR2F1c3NpYW5CbHVyIHJlc3VsdD0iYmx1ck91dCIgaW49Im9mZk91dCIgc3RkRGV2aWF0aW9uPSIxLjUiLz4KICAgICAgICAgICAgPGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjQgMCIvPgogICAgICAgICAgICA8ZmVNZXJnZT4KICAgICAgICAgICAgICAgIDxmZU1lcmdlTm9kZS8+CiAgICAgICAgICAgICAgICA8ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiLz4KICAgICAgICAgICAgPC9mZU1lcmdlPgogICAgICAgIDwvZmlsdGVyPgogICAgPC9kZWZzPgo8cGF0aCBmaWxsPSIjZmZmZmZmIiBmaWx0ZXI9InVybCgjYSkiIGQ9Ik0zMS4wNTksNS4zOTVjMTYuODktMi43MjcsMzIuODE3LDguNzcyLDM1LjU0NSwyNS42NjJjMi43MjcsMTYuODktOC43NzIsMzIuODE3LTI1LjY2MiwzNS41NDUKCUMyNC4wNTEsNjkuMzI5LDguMTI0LDU3LjgzLDUuMzk3LDQwLjk0QzIuNjcsMjQuMDQ5LDE0LjE2OSw4LjEyMiwzMS4wNTksNS4zOTV6Ii8+CjxwYXRoIGZpbGw9IiMwMDY4YzMiIGQ9Ik01NS4zNjQsMTcuMjAyYy01LjA0Ny01LjE5Ny0xMS44MDItOC4xMDktMTkuMDItOC4yYy03LjE5MS0wLjA5LTEzLjk4NSwyLjY2OS0xOS4xNDksNy43NjgKCUMxMS45MSwyMS45ODksOSwyOC45NTUsOSwzNi4zOTJsMC4wNDQsMS4yNmMwLDEuMTgxLDAuOTYxLDIuMTQyLDIuMTQyLDIuMTQyczIuMTQxLTAuOTYsMi4xNDItMi4xNGwwLjAxLTAuOTEzCgljMC05Ljk0NSw4LjQ1My0yMC41OTMsMjEuMDM1LTIwLjU5M2MxMy4xMzIsMCwyMS4yNjEsMTAuNzEyLDIxLjI2MSwyMC42MzdjMCw1LjE3My0yLjA2Myw5LjkxOS01LjgwOCwxMy4zNjMKCUM0Ni4xNyw1My41MDksNDEuMjYsNTUuMzYsMzYsNTUuMzZjLTMuMTMsMC02LjIxOS0wLjc1NS04Ljk1OC0yLjE4NmwxOC44Ny04LjY4NmMxLjI2Ny0wLjU4MywyLjExNS0xLjgxLDIuMjEzLTMuMjAxCglzLTAuNTY5LTIuNzI1LTEuNzU0LTMuNDg3bC0xNS43MDYtOC40MTVjLTAuNTU0LTAuMzU3LTEuMjEzLTAuNDc3LTEuODU4LTAuMzM3Yy0wLjY0NCwwLjEzOS0xLjE5NSwwLjUyMS0xLjU1MiwxLjA3NQoJYy0wLjczNywxLjE0NC0wLjQwNiwyLjY3MywwLjcyMSwzLjM5N2w4LjQ1NCw2LjkyM2wtMTguNDE5LDguNDc4Yy0xLjQyNywwLjY1Ny0yLjI5OCwyLjEtMi4yMTgsMy42NzUKCWMwLjA0OCwwLjk0OSwwLjQ5MywxLjg4NCwxLjI1MywyLjYzNEMyMi4xNTgsNjAuMjY5LDI4Ljg0LDYzLDM1Ljk4NSw2M2MwLjQ0NSwwLDAuODkyLTAuMDExLDEuMzQxLTAuMDMyCgljMTQuMTU2LTAuNjczLDI1LjQzMi0xMi4zMTUsMjUuNjcxLTI2LjUwNUM2My4xMTgsMjkuMjM2LDYwLjQwNywyMi4zOTYsNTUuMzY0LDE3LjIwMnoiLz4KPC9zdmc+Cg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"22248c4c-7a12-46e2-9a41-44291b373a4d": {
|
||||
"name": "LogMeOnce",
|
||||
"icon_dark": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMjA0OCAyMDQ4IiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTSAwIDAgTCAtNSAxIEwgLTE1IDQgTCAtMzggMTEgTCAtNTcgMTggTCAtNzYgMjkgTCAtOTAgMzggTCAtMTA5IDUyIEwgLTExNiA1OSBMIC0xMjcgNjggTCAtMTQwIDgxIEwgLTE0OCA4OCBMIC0xNDkgOTEgTCAtMTUxIDkxIEwgLTE2MSAxMDEgTCAtMTY5IDEwOCBMIC0xNjkgMTEwIEwgLTE3MSAxMTAgTCAtMTgwIDExOSBMIC0xODggMTI2IEwgLTIwMSAxMzcgTCAtMjEyIDE0NiBMIC0yMjYgMTU3IEwgLTIzOSAxNjcgTCAtMjUwIDE3NSBMIC0yNzAgMTg4IEwgLTI5MSAyMDIgTCAtMzA5IDIxMiBMIC0zMzAgMjI0IEwgLTM1MyAyMzUgTCAtMzg2IDI1MCBMIC00MDcgMjU5IEwgLTQ0OSAyNzMgTCAtNDcyIDI4MSBMIC01MDIgMjg5IEwgLTU1MSAyOTggTCAtNjE1IDMwOSBMIC02NDcgMzE2IEwgLTY4MSAzMjQgTCAtNjk4IDMzMCBMIC03MTQgMzM4IEwgLTczNSAzNTEgTCAtNzQ1IDM1OCBMIC03NTYgMzY3IEwgLTc2NCAzNzUgTCAtNzY2IDM3OSBMIC03NjggMzc5IEwgLTc3MyAzODUgTCAtNzgxIDM5NSBMIC03OTEgNDEwIEwgLTgwMSA0MjggTCAtODEwIDQ0NyBMIC04MTYgNDY1IEwgLTgyMiA1MDAgTCAtODI3IDU0NSBMIC04MzAgNTkyIEwgLTgzMiA2NTMgTCAtODMyIDcxMiBMIC04MzEgNzQyIEwgLTgyOCA3OTQgTCAtODIyIDg2NiBMIC04MTYgOTI4IEwgLTgxMiA5NTcgTCAtODAwIDEwMjAgTCAtNzg5IDEwNzIgTCAtNzgzIDEwOTggTCAtNzY1IDExNjIgTCAtNzUxIDEyMDcgTCAtNzQ0IDEyMjUgTCAtNzM0IDEyNTQgTCAtNzIwIDEyOTAgTCAtNzA0IDEzMjggTCAtNjg5IDEzNjEgTCAtNjY4IDE0MDMgTCAtNjUzIDE0MzEgTCAtNjM5IDE0NTYgTCAtNjIwIDE0ODggTCAtNjA3IDE1MDkgTCAtNTk0IDE1MjkgTCAtNTg0IDE1NDQgTCAtNTcxIDE1NjMgTCAtNTU4IDE1ODEgTCAtNTQ0IDE2MDAgTCAtNTMzIDE2MTUgTCAtNTIwIDE2MzIgTCAtNTA3IDE2NDggTCAtNDc5IDE2ODIgTCAtNDcwIDE2OTMgTCAtNDYwIDE3MDQgTCAtNDQ5IDE3MTYgTCAtNDQyIDE3MjQgTCAtNDMyIDE3MzQgTCAtNDI1IDE3NDIgTCAtMzY4IDE3OTkgTCAtMzUxIDE4MTUgTCAtMzM1IDE4MzAgTCAtMzI3IDE4MzcgTCAtMzE0IDE4NDkgTCAtMjg4IDE4NzEgTCAtMjcxIDE4ODUgTCAtMjYxIDE4OTMgTCAtMjQ0IDE5MDcgTCAtMjI4IDE5MTkgTCAtMjE1IDE5MjkgTCAtMTk2IDE5NDMgTCAtMTg0IDE5NTIgTCAtMTY4IDE5NjMgTCAtMTQ4IDE5NzcgTCAtMTMzIDE5ODcgTCAtMTEyIDIwMDAgTCAtODggMjAxMyBMIC03MiAyMDIxIEwgLTUyIDIwMzAgTCAtMzIgMjAzNyBMIDEyIDIwNDggTCA2MCAyMDQ4IEwgNjEgMjA0NyBMIDczIDIwNDQgTCAxMDAgMjAzNyBMIDEyMSAyMDI5IEwgMTQ5IDIwMTUgTCAxNjcgMjAwNSBMIDE4NiAxOTk0IEwgMjEwIDE5NzkgTCAyMzEgMTk2NSBMIDI2NiAxOTQxIEwgMjg0IDE5MjggTCAyOTUgMTkyMCBMIDMwNiAxOTExIEwgMzI0IDE4OTggTCAzMzggMTg4NyBMIDM0OSAxODc3IEwgMzU4IDE4NzAgTCAzNjkgMTg2MCBMIDM4MCAxODUxIEwgMzgwIDE4NDkgTCAzODIgMTg0OSBMIDM4NSAxODQ2IEwgMzk2IDE4MzcgTCA0MDMgMTgzMCBMIDQxMSAxODIzIEwgNDE4IDE4MTYgTCA0MjYgMTgwOSBMIDQ3NSAxNzYwIEwgNDc3IDE3NTYgTCA0NzkgMTc1NiBMIDQ4NyAxNzQ3IEwgNTAzIDE3MzAgTCA1MTAgMTcyMiBMIDUxOSAxNzEyIEwgNTI2IDE3MDQgTCA1MzggMTY5MSBMIDU0NyAxNjgwIEwgNTU4IDE2NjcgTCA1NjYgMTY1NiBMIDU3OSAxNjQwIEwgNTkwIDE2MjYgTCA2MDYgMTYwNSBMIDYyMSAxNTgzIEwgNjMxIDE1NjkgTCA2NDUgMTU0OSBMIDY1NSAxNTMzIEwgNjcwIDE1MTAgTCA2ODUgMTQ4NSBMIDY5NiAxNDY3IEwgNzA2IDE0NDkgTCA3MzAgMTQwNSBMIDc0NSAxMzc0IEwgNzYyIDEzMzggTCA3NzQgMTMxMCBMIDc5MiAxMjY3IEwgODExIDEyMTYgTCA4MjcgMTE2NiBMIDg0MiAxMTE3IEwgODU4IDEwNTEgTCA4NzAgOTk2IEwgODc1IDk2NyBMIDg4MSA5MTcgTCA4ODYgODgyIEwgODkxIDg0NiBMIDg5NCA4MTIgTCA4OTYgNzc0IEwgODk2IDY1NSBMIDg5NSA2MzAgTCA4OTIgNTkzIEwgODg4IDU1OSBMIDg3NyA0NzAgTCA4NzIgNDQ1IEwgODY1IDQyNiBMIDg1NyA0MTEgTCA4NDcgMzk1IEwgODM0IDM3OCBMIDgxNCAzNTggTCA4MDAgMzQ3IEwgNzg1IDMzNyBMIDc2NiAzMjcgTCA3NTAgMzIxIEwgNzI3IDMxNSBMIDY5MyAzMDggTCA2NTAgMzAxIEwgNTk3IDI5MiBMIDU3MCAyODYgTCA1NDggMjgwIEwgNTE1IDI3MCBMIDQ4NyAyNjEgTCA0NTkgMjUwIEwgNDM4IDI0MSBMIDQxNCAyMzAgTCAzODIgMjEzIEwgMzU4IDE5OSBMIDM0NyAxOTIgTCAzMzAgMTgwIEwgMzEyIDE2NyBMIDMwMCAxNTcgTCAyODYgMTQ2IEwgMjcxIDEzMyBMIDI2MyAxMjYgTCAyNTEgMTE1IEwgMjQwIDEwNiBMIDIyOCA5NSBMIDIwMCA3MSBMIDE4OSA2MiBMIDE3NCA1MCBMIDE1OCAzOCBMIDE0MiAyNyBMIDEyNyAxOSBMIDExNSAxNCBMIDk1IDggTCA3MSAwIEwgMCAwIHogTSAzMCA2MjIgTCA1MSA2MjMgTCA3OSA2MjcgTCAxMDAgNjMzIEwgMTIxIDY0MSBMIDE0MSA2NTIgTCAxNTcgNjYzIEwgMTcyIDY3NiBMIDE4NiA2OTAgTCAxODYgNjkyIEwgMTg4IDY5MiBMIDIwNiA3MTYgTCAyMTYgNzM0IEwgMjIxIDc0NSBMIDIyNiA3NTggTCAyMzMgNzg0IEwgMjM2IDc5OCBMIDIzNyA4MDggTCAyMzcgODQwIEwgMjMzIDg2NSBMIDIyNiA4OTEgTCAyMTkgOTA5IEwgMjA5IDkyOCBMIDE5NSA5NDkgTCAxODYgOTU5IEwgMTc5IDk2NyBMIDE2NiA5ODAgTCAxNTUgOTg5IEwgMTQyIDk5OCBMIDEyNiAxMDA3IEwgMTEzIDEwMTIgTCAxMTQgMTAyNCBMIDEyMiAxMDYwIEwgMTM3IDExMjMgTCAxNTYgMTIwOCBMIDE3MCAxMjcxIEwgMTc0IDEyOTYgTCAxNzYgMTMxMCBMIDE3NiAxMzM1IEwgMTczIDEzNDkgTCAxNjYgMTM1OSBMIDE1NyAxMzY3IEwgMTQ2IDEzNzIgTCAxNDAgMTM3NCBMIDExOSAxMzc2IEwgNTEgMTM3NiBMIC0zMiAxMzc3IEwgLTY0IDEzNzcgTCAtNzggMTM3NCBMIC04NSAxMzcwIEwgLTk4IDEzNTkgTCAtMTA2IDEzNDkgTCAtMTEwIDEzNDEgTCAtMTEyIDEzMzMgTCAtMTEyIDEzMTggTCAtMTA3IDEyODggTCAtMTAwIDEyNTYgTCAtODUgMTE5NiBMIC02OSAxMTI0IEwgLTU3IDEwNzIgTCAtNTAgMTAzNiBMIC00NyAxMDExIEwgLTUzIDEwMDkgTCAtNjkgMTAwMSBMIC04MSA5OTQgTCAtOTMgOTg1IEwgLTEwMyA5NzYgTCAtMTExIDk2OSBMIC0xMjAgOTYwIEwgLTEzMSA5NDYgTCAtMTQyIDkyOSBMIC0xNTIgOTEwIEwgLTE2MCA4OTAgTCAtMTY2IDg3MCBMIC0xNjkgODUzIEwgLTE3MCA4NDQgTCAtMTcwIDgxNCBMIC0xNjcgNzk0IEwgLTE2MSA3NjcgTCAtMTU0IDc0NiBMIC0xMzcgNzEzIEwgLTEyNiA2OTggTCAtMTE5IDY5MCBMIC0xMDggNjc4IEwgLTkzIDY2NSBMIC03NyA2NTMgTCAtNTYgNjQxIEwgLTM4IDYzMyBMIC0xNiA2MjcgTCAzIDYyNCBMIDMwIDYyMiB6ICIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOTkxKSIgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgLz4KPC9zdmc+Cg==",
|
||||
"icon_light": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyMDQ4IDIwNDgiIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5OTEpIiBkPSJtMCAwaDcxbDI0IDggMjAgNiAxMiA1IDE1IDggMTYgMTEgMTYgMTIgMTUgMTIgMTEgOSAyOCAyNCAxMiAxMSAxMSA5IDEyIDExIDggNyAxNSAxMyAxNCAxMSAxMiAxMCAxOCAxMyAxNyAxMiAxMSA3IDI0IDE0IDMyIDE3IDI0IDExIDIxIDkgMjggMTEgMjggOSAzMyAxMCAyMiA2IDI3IDYgNTMgOSA0MyA3IDM0IDcgMjMgNiAxNiA2IDE5IDEwIDE1IDEwIDE0IDExIDIwIDIwIDEzIDE3IDEwIDE2IDggMTUgNyAxOSA1IDI1IDExIDg5IDQgMzQgMyAzNyAxIDI1djExOWwtMiAzOC0zIDM0LTUgMzYtNSAzNS02IDUwLTUgMjktMTIgNTUtMTYgNjYtMTUgNDktMTYgNTAtMTkgNTEtMTggNDMtMTIgMjgtMTcgMzYtMTUgMzEtMjQgNDQtMTAgMTgtMTEgMTgtMTUgMjUtMTUgMjMtMTAgMTYtMTQgMjAtMTAgMTQtMTUgMjItMTYgMjEtMTEgMTQtMTMgMTYtOCAxMS0xMSAxMy05IDExLTEyIDEzLTcgOC05IDEwLTcgOC0xNiAxNy04IDloLTJsLTIgNC00OSA0OS04IDctNyA3LTggNy03IDctMTEgOS0zIDNoLTJ2MmwtMTEgOS0xMSAxMC05IDctMTEgMTAtMTQgMTEtMTggMTMtMTEgOS0xMSA4LTE4IDEzLTM1IDI0LTIxIDE0LTI0IDE1LTE5IDExLTE4IDEwLTI4IDE0LTIxIDgtMjcgNy0xMiAzLTEgMWgtNDhsLTQ0LTExLTIwLTctMjAtOS0xNi04LTI0LTEzLTIxLTEzLTE1LTEwLTIwLTE0LTE2LTExLTEyLTktMTktMTQtMTMtMTAtMTYtMTItMTctMTQtMTAtOC0xNy0xNC0yNi0yMi0xMy0xMi04LTctMTYtMTUtMTctMTYtNTctNTctNy04LTEwLTEwLTctOC0xMS0xMi0xMC0xMS05LTExLTI4LTM0LTEzLTE2LTEzLTE3LTExLTE1LTE0LTE5LTEzLTE4LTEzLTE5LTEwLTE1LTEzLTIwLTEzLTIxLTE5LTMyLTE0LTI1LTE1LTI4LTIxLTQyLTE1LTMzLTE2LTM4LTE0LTM2LTEwLTI5LTctMTgtMTQtNDUtMTgtNjQtNi0yNi0xMS01Mi0xMi02My00LTI5LTYtNjItNi03Mi0zLTUyLTEtMzB2LTU5bDItNjEgMy00NyA1LTQ1IDYtMzUgNi0xOCA5LTE5IDEwLTE4IDEwLTE1IDgtMTAgNS02aDJsMi00IDgtOCAxMS05IDEwLTcgMjEtMTMgMTYtOCAxNy02IDM0LTggMzItNyA2NC0xMSA0OS05IDMwLTggMjMtOCA0Mi0xNCAyMS05IDMzLTE1IDIzLTExIDIxLTEyIDE4LTEwIDIxLTE0IDIwLTEzIDExLTggMTMtMTAgMTQtMTEgMTEtOSAxMy0xMSA4LTcgOS05aDJ2LTJsOC03IDEwLTEwaDJsMS0zIDgtNyAxMy0xMyAxMS05IDctNyAxOS0xNCAxNC05IDE5LTExIDE5LTcgMjMtNyAxMC0zeiIgZmlsbD0iI0YxODQyOSIvPgo8cGF0aCB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMDIxLDYyMikiIGQ9Im0wIDAgMjEgMSAyOCA0IDIxIDYgMjEgOCAyMCAxMSAxNiAxMSAxNSAxMyAxNCAxNHYyaDJsMTggMjQgMTAgMTggNSAxMSA1IDEzIDcgMjYgMyAxNCAxIDEwdjMybC00IDI1LTcgMjYtNyAxOC0xMCAxOS0xNCAyMS05IDEwLTcgOC0xMyAxMy0xMSA5LTEzIDktMTYgOS0xMyA1IDEgMTIgOCAzNiAxNSA2MyAxOSA4NSAxNCA2MyA0IDI1IDIgMTR2MjVsLTMgMTQtNyAxMC05IDgtMTEgNS02IDItMjEgMmgtNjhsLTgzIDFoLTMybC0xNC0zLTctNC0xMy0xMS04LTEwLTQtOC0yLTh2LTE1bDUtMzAgNy0zMiAxNS02MCAxNi03MiAxMi01MiA3LTM2IDMtMjUtNi0yLTE2LTgtMTItNy0xMi05LTEwLTktOC03LTktOS0xMS0xNC0xMS0xNy0xMC0xOS04LTIwLTYtMjAtMy0xNy0xLTl2LTMwbDMtMjAgNi0yNyA3LTIxIDE3LTMzIDExLTE1IDctOCAxMS0xMiAxNS0xMyAxNi0xMiAyMS0xMiAxOC04IDIyLTYgMTktM3oiIGZpbGw9IiNGRUZGRkUiLz4KPC9zdmc+Cg=="
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@
|
||||
<tr>
|
||||
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
|
||||
{% blocktrans with expires=expires|naturaltime %}
|
||||
If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
|
||||
If you did not request a password change, please ignore this email. The link above is valid for {{ expires }}.
|
||||
{% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -5,7 +5,7 @@ You recently requested to change your password for your authentik account. Use t
|
||||
{% endblocktrans %}
|
||||
{{ url }}
|
||||
{% blocktrans with expires=expires|naturaltime %}
|
||||
If you did not request a password change, please ignore this Email. The link above is valid for {{ expires }}.
|
||||
If you did not request a password change, please ignore this email. The link above is valid for {{ expires }}.
|
||||
{% endblocktrans %}
|
||||
|
||||
--
|
||||
|
0
authentik/stages/redirect/__init__.py
Normal file
0
authentik/stages/redirect/__init__.py
Normal file
42
authentik/stages/redirect/api.py
Normal file
42
authentik/stages/redirect/api.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""RedirectStage API Views"""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.flows.api.stages import StageSerializer
|
||||
from authentik.stages.redirect.models import RedirectMode, RedirectStage
|
||||
|
||||
|
||||
class RedirectStageSerializer(StageSerializer):
|
||||
"""RedirectStage Serializer"""
|
||||
|
||||
def validate(self, attrs):
|
||||
mode = attrs.get("mode")
|
||||
target_static = attrs.get("target_static")
|
||||
target_flow = attrs.get("target_flow")
|
||||
if mode == RedirectMode.STATIC and not target_static:
|
||||
raise ValidationError(_("Target URL should be present when mode is Static."))
|
||||
if mode == RedirectMode.FLOW and not target_flow:
|
||||
raise ValidationError(_("Target Flow should be present when mode is Flow."))
|
||||
return attrs
|
||||
|
||||
class Meta:
|
||||
model = RedirectStage
|
||||
fields = StageSerializer.Meta.fields + [
|
||||
"keep_context",
|
||||
"mode",
|
||||
"target_static",
|
||||
"target_flow",
|
||||
]
|
||||
|
||||
|
||||
class RedirectStageViewSet(UsedByMixin, ModelViewSet):
|
||||
"""RedirectStage Viewset"""
|
||||
|
||||
queryset = RedirectStage.objects.all()
|
||||
serializer_class = RedirectStageSerializer
|
||||
filterset_fields = ["name"]
|
||||
search_fields = ["name"]
|
||||
ordering = ["name"]
|
11
authentik/stages/redirect/apps.py
Normal file
11
authentik/stages/redirect/apps.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""authentik redirect app"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthentikStageRedirectConfig(AppConfig):
|
||||
"""authentik redirect app"""
|
||||
|
||||
name = "authentik.stages.redirect"
|
||||
label = "authentik_stages_redirect"
|
||||
verbose_name = "authentik Stages.Redirect"
|
49
authentik/stages/redirect/migrations/0001_initial.py
Normal file
49
authentik/stages/redirect/migrations/0001_initial.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.0.10 on 2024-12-11 14:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0027_auto_20231028_1424"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="RedirectStage",
|
||||
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",
|
||||
),
|
||||
),
|
||||
("keep_context", models.BooleanField(default=True)),
|
||||
("mode", models.TextField(choices=[("static", "Static"), ("flow", "Flow")])),
|
||||
("target_static", models.CharField(blank=True, default="")),
|
||||
(
|
||||
"target_flow",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Redirect Stage",
|
||||
"verbose_name_plural": "Redirect Stages",
|
||||
},
|
||||
bases=("authentik_flows.stage",),
|
||||
),
|
||||
]
|
0
authentik/stages/redirect/migrations/__init__.py
Normal file
0
authentik/stages/redirect/migrations/__init__.py
Normal file
49
authentik/stages/redirect/models.py
Normal file
49
authentik/stages/redirect/models.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""authentik redirect stage"""
|
||||
|
||||
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 Flow, Stage
|
||||
|
||||
|
||||
class RedirectMode(models.TextChoices):
|
||||
"""Mode a Redirect stage can operate in"""
|
||||
|
||||
STATIC = "static"
|
||||
FLOW = "flow"
|
||||
|
||||
|
||||
class RedirectStage(Stage):
|
||||
"""Redirect the user to another flow, potentially with all gathered context"""
|
||||
|
||||
keep_context = models.BooleanField(default=True)
|
||||
mode = models.TextField(choices=RedirectMode.choices)
|
||||
target_static = models.CharField(blank=True, default="")
|
||||
target_flow = models.ForeignKey(
|
||||
Flow,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[BaseSerializer]:
|
||||
from authentik.stages.redirect.api import RedirectStageSerializer
|
||||
|
||||
return RedirectStageSerializer
|
||||
|
||||
@property
|
||||
def view(self) -> type[View]:
|
||||
from authentik.stages.redirect.stage import RedirectStageView
|
||||
|
||||
return RedirectStageView
|
||||
|
||||
@property
|
||||
def component(self) -> str:
|
||||
return "ak-stage-redirect-form"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Redirect Stage")
|
||||
verbose_name_plural = _("Redirect Stages")
|
110
authentik/stages/redirect/stage.py
Normal file
110
authentik/stages/redirect/stage.py
Normal file
@ -0,0 +1,110 @@
|
||||
"""authentik redirect stage"""
|
||||
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from django.http.response import HttpResponse
|
||||
from rest_framework.fields import CharField
|
||||
|
||||
from authentik.flows.challenge import (
|
||||
Challenge,
|
||||
ChallengeResponse,
|
||||
RedirectChallenge,
|
||||
)
|
||||
from authentik.flows.exceptions import FlowNonApplicableException
|
||||
from authentik.flows.models import (
|
||||
Flow,
|
||||
)
|
||||
from authentik.flows.planner import (
|
||||
PLAN_CONTEXT_IS_REDIRECTED,
|
||||
PLAN_CONTEXT_REDIRECT_STAGE_TARGET,
|
||||
FlowPlanner,
|
||||
)
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN, InvalidStageError
|
||||
from authentik.lib.utils.urls import reverse_with_qs
|
||||
from authentik.stages.redirect.models import RedirectMode, RedirectStage
|
||||
|
||||
URL_SCHEME_FLOW = "ak-flow"
|
||||
|
||||
|
||||
class RedirectChallengeResponse(ChallengeResponse):
|
||||
"""Redirect challenge response"""
|
||||
|
||||
component = CharField(default="xak-flow-redirect")
|
||||
to = CharField()
|
||||
|
||||
|
||||
class RedirectStageView(ChallengeStageView):
|
||||
"""Redirect stage to redirect to other Flows with context"""
|
||||
|
||||
response_class = RedirectChallengeResponse
|
||||
|
||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||
return self.executor.stage_ok()
|
||||
|
||||
def parse_target(self, target: str) -> str | Flow:
|
||||
parsed_target = urlsplit(target)
|
||||
|
||||
if parsed_target.scheme != URL_SCHEME_FLOW:
|
||||
return target
|
||||
|
||||
flow = Flow.objects.filter(slug=parsed_target.netloc).first()
|
||||
if not flow:
|
||||
self.logger.warning(
|
||||
f"Flow set by {PLAN_CONTEXT_REDIRECT_STAGE_TARGET} does not exist",
|
||||
flow_slug=parsed_target.path,
|
||||
)
|
||||
return flow
|
||||
|
||||
def switch_flow_with_context(self, flow: Flow, keep_context=True) -> str:
|
||||
"""Switch to another flow, optionally keeping all context"""
|
||||
self.logger.info(
|
||||
"f(exec): Switching to new flow", new_flow=flow.slug, keep_context=keep_context
|
||||
)
|
||||
planner = FlowPlanner(flow)
|
||||
planner.use_cache = False
|
||||
default_context = self.executor.plan.context if keep_context else {}
|
||||
try:
|
||||
default_context[PLAN_CONTEXT_IS_REDIRECTED] = self.executor.flow
|
||||
plan = planner.plan(self.request, default_context)
|
||||
except FlowNonApplicableException as exc:
|
||||
raise InvalidStageError() from exc
|
||||
self.request.session[SESSION_KEY_PLAN] = plan
|
||||
kwargs = self.executor.kwargs
|
||||
kwargs.update({"flow_slug": flow.slug})
|
||||
return reverse_with_qs("authentik_core:if-flow", self.request.GET, kwargs=kwargs)
|
||||
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
"""Get the redirect target. Prioritize `redirect_stage_target` if present."""
|
||||
|
||||
current_stage: RedirectStage = self.executor.current_stage
|
||||
target: str | Flow = ""
|
||||
|
||||
target_url_override = self.executor.plan.context.get(PLAN_CONTEXT_REDIRECT_STAGE_TARGET, "")
|
||||
if target_url_override:
|
||||
target = self.parse_target(target_url_override)
|
||||
# `target` is falsy if the override was to a Flow but that Flow doesn't exist.
|
||||
if not target:
|
||||
if current_stage.mode == RedirectMode.STATIC:
|
||||
target = current_stage.target_static
|
||||
if current_stage.mode == RedirectMode.FLOW:
|
||||
target = current_stage.target_flow
|
||||
|
||||
if isinstance(target, str):
|
||||
redirect_to = target
|
||||
else:
|
||||
redirect_to = self.switch_flow_with_context(
|
||||
target, keep_context=current_stage.keep_context
|
||||
)
|
||||
|
||||
if not redirect_to:
|
||||
raise InvalidStageError(
|
||||
"No target found for Redirect stage. The stage's target_flow may have been deleted."
|
||||
)
|
||||
|
||||
return RedirectChallenge(
|
||||
data={
|
||||
"component": "xak-flow-redirect",
|
||||
"to": redirect_to,
|
||||
}
|
||||
)
|
172
authentik/stages/redirect/tests.py
Normal file
172
authentik/stages/redirect/tests.py
Normal file
@ -0,0 +1,172 @@
|
||||
"""Test Redirect stage"""
|
||||
|
||||
from django.urls.base import reverse
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.flows.models import FlowAuthenticationRequirement, FlowDesignation, FlowStageBinding
|
||||
from authentik.flows.tests import FlowTestCase
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.expression.models import ExpressionPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.stages.dummy.models import DummyStage
|
||||
from authentik.stages.redirect.api import RedirectStageSerializer
|
||||
from authentik.stages.redirect.models import RedirectMode, RedirectStage
|
||||
|
||||
URL = "https://url.test/"
|
||||
URL_OVERRIDE = "https://urloverride.test/"
|
||||
|
||||
|
||||
class TestRedirectStage(FlowTestCase):
|
||||
"""Test Redirect stage API"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.target_flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
self.dummy_stage = DummyStage.objects.create(name="dummy")
|
||||
FlowStageBinding.objects.create(target=self.target_flow, stage=self.dummy_stage, order=0)
|
||||
self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
self.stage = RedirectStage.objects.create(
|
||||
name="redirect",
|
||||
keep_context=True,
|
||||
mode=RedirectMode.STATIC,
|
||||
target_static=URL,
|
||||
target_flow=self.target_flow,
|
||||
)
|
||||
self.binding = FlowStageBinding.objects.create(
|
||||
target=self.flow,
|
||||
stage=self.stage,
|
||||
order=0,
|
||||
)
|
||||
|
||||
def test_static(self):
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(response, URL)
|
||||
|
||||
def test_flow(self):
|
||||
self.stage.mode = RedirectMode.FLOW
|
||||
self.stage.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(
|
||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": self.target_flow.slug})
|
||||
)
|
||||
|
||||
def test_override_static(self):
|
||||
policy = ExpressionPolicy.objects.create(
|
||||
name=generate_id(),
|
||||
expression=f"context['flow_plan'].context['redirect_stage_target'] = "
|
||||
f"'{URL_OVERRIDE}'; return True",
|
||||
)
|
||||
PolicyBinding.objects.create(policy=policy, target=self.binding, order=0)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(response, URL_OVERRIDE)
|
||||
|
||||
def test_override_flow(self):
|
||||
target_flow_override = create_test_flow(FlowDesignation.AUTHENTICATION)
|
||||
dummy_stage_override = DummyStage.objects.create(name="dummy_override")
|
||||
FlowStageBinding.objects.create(
|
||||
target=target_flow_override, stage=dummy_stage_override, order=0
|
||||
)
|
||||
policy = ExpressionPolicy.objects.create(
|
||||
name=generate_id(),
|
||||
expression=f"context['flow_plan'].context['redirect_stage_target'] = "
|
||||
f"'ak-flow://{target_flow_override.slug}'; return True",
|
||||
)
|
||||
PolicyBinding.objects.create(policy=policy, target=self.binding, order=0)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(
|
||||
response,
|
||||
reverse("authentik_core:if-flow", kwargs={"flow_slug": target_flow_override.slug}),
|
||||
)
|
||||
|
||||
def test_override_nonexistant_flow(self):
|
||||
policy = ExpressionPolicy.objects.create(
|
||||
name=generate_id(),
|
||||
expression="context['flow_plan'].context['redirect_stage_target'] = "
|
||||
"'ak-flow://nonexistent'; return True",
|
||||
)
|
||||
PolicyBinding.objects.create(policy=policy, target=self.binding, order=0)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(response, URL)
|
||||
|
||||
def test_target_flow_requires_redirect(self):
|
||||
self.target_flow.authentication = FlowAuthenticationRequirement.REQUIRE_REDIRECT
|
||||
self.target_flow.save()
|
||||
self.stage.mode = RedirectMode.FLOW
|
||||
self.stage.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(
|
||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": self.target_flow.slug})
|
||||
)
|
||||
|
||||
def test_target_flow_non_applicable(self):
|
||||
self.target_flow.authentication = FlowAuthenticationRequirement.REQUIRE_AUTHENTICATED
|
||||
self.target_flow.save()
|
||||
self.stage.mode = RedirectMode.FLOW
|
||||
self.stage.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
)
|
||||
|
||||
self.assertStageResponse(response, component="ak-stage-access-denied")
|
||||
|
||||
def test_serializer(self):
|
||||
with self.assertRaises(ValidationError):
|
||||
RedirectStageSerializer(
|
||||
data={
|
||||
"name": generate_id(20),
|
||||
"mode": RedirectMode.STATIC,
|
||||
}
|
||||
).is_valid(raise_exception=True)
|
||||
|
||||
self.assertTrue(
|
||||
RedirectStageSerializer(
|
||||
data={
|
||||
"name": generate_id(20),
|
||||
"mode": RedirectMode.STATIC,
|
||||
"target_static": URL,
|
||||
}
|
||||
).is_valid(raise_exception=True)
|
||||
)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
RedirectStageSerializer(
|
||||
data={
|
||||
"name": generate_id(20),
|
||||
"mode": RedirectMode.FLOW,
|
||||
}
|
||||
).is_valid(raise_exception=True)
|
||||
|
||||
self.assertTrue(
|
||||
RedirectStageSerializer(
|
||||
data={
|
||||
"name": generate_id(20),
|
||||
"mode": RedirectMode.FLOW,
|
||||
"target_flow": create_test_flow().flow_uuid,
|
||||
}
|
||||
).is_valid(raise_exception=True)
|
||||
)
|
5
authentik/stages/redirect/urls.py
Normal file
5
authentik/stages/redirect/urls.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""API URLs"""
|
||||
|
||||
from authentik.stages.redirect.api import RedirectStageViewSet
|
||||
|
||||
api_urlpatterns = [("stages/redirect", RedirectStageViewSet)]
|
@ -2801,6 +2801,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"identifiers"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"const": "authentik_stages_redirect.redirectstage"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"absent",
|
||||
"present",
|
||||
"created",
|
||||
"must_created"
|
||||
],
|
||||
"default": "present"
|
||||
},
|
||||
"conditions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/$defs/model_authentik_stages_redirect.redirectstage_permissions"
|
||||
},
|
||||
"attrs": {
|
||||
"$ref": "#/$defs/model_authentik_stages_redirect.redirectstage"
|
||||
},
|
||||
"identifiers": {
|
||||
"$ref": "#/$defs/model_authentik_stages_redirect.redirectstage"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -4023,6 +4063,7 @@
|
||||
"require_authenticated",
|
||||
"require_unauthenticated",
|
||||
"require_superuser",
|
||||
"require_redirect",
|
||||
"require_outpost"
|
||||
],
|
||||
"title": "Authentication",
|
||||
@ -4493,6 +4534,7 @@
|
||||
"authentik.stages.invitation",
|
||||
"authentik.stages.password",
|
||||
"authentik.stages.prompt",
|
||||
"authentik.stages.redirect",
|
||||
"authentik.stages.user_delete",
|
||||
"authentik.stages.user_login",
|
||||
"authentik.stages.user_logout",
|
||||
@ -4588,6 +4630,7 @@
|
||||
"authentik_stages_password.passwordstage",
|
||||
"authentik_stages_prompt.prompt",
|
||||
"authentik_stages_prompt.promptstage",
|
||||
"authentik_stages_redirect.redirectstage",
|
||||
"authentik_stages_user_delete.userdeletestage",
|
||||
"authentik_stages_user_login.userloginstage",
|
||||
"authentik_stages_user_logout.userlogoutstage",
|
||||
@ -6813,6 +6856,10 @@
|
||||
"authentik_stages_prompt.delete_promptstage",
|
||||
"authentik_stages_prompt.view_prompt",
|
||||
"authentik_stages_prompt.view_promptstage",
|
||||
"authentik_stages_redirect.add_redirectstage",
|
||||
"authentik_stages_redirect.change_redirectstage",
|
||||
"authentik_stages_redirect.delete_redirectstage",
|
||||
"authentik_stages_redirect.view_redirectstage",
|
||||
"authentik_stages_source.add_sourcestage",
|
||||
"authentik_stages_source.change_sourcestage",
|
||||
"authentik_stages_source.delete_sourcestage",
|
||||
@ -6976,6 +7023,16 @@
|
||||
"title": "Krb5 conf",
|
||||
"description": "Custom krb5.conf to use. Uses the system one by default"
|
||||
},
|
||||
"kadmin_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"MIT",
|
||||
"Heimdal",
|
||||
"other"
|
||||
],
|
||||
"title": "Kadmin type",
|
||||
"description": "KAdmin server type"
|
||||
},
|
||||
"sync_users": {
|
||||
"type": "boolean",
|
||||
"title": "Sync users",
|
||||
@ -11475,6 +11532,146 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_authentik_stages_redirect.redirectstage": {
|
||||
"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"
|
||||
},
|
||||
"keep_context": {
|
||||
"type": "boolean",
|
||||
"title": "Keep context"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"static",
|
||||
"flow"
|
||||
],
|
||||
"title": "Mode"
|
||||
},
|
||||
"target_static": {
|
||||
"type": "string",
|
||||
"title": "Target static"
|
||||
},
|
||||
"target_flow": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Target flow"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"model_authentik_stages_redirect.redirectstage_permissions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permission"
|
||||
],
|
||||
"properties": {
|
||||
"permission": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"add_redirectstage",
|
||||
"change_redirectstage",
|
||||
"delete_redirectstage",
|
||||
"view_redirectstage"
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
"type": "integer"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"model_authentik_stages_user_delete.userdeletestage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -12820,6 +13017,10 @@
|
||||
"authentik_stages_prompt.delete_promptstage",
|
||||
"authentik_stages_prompt.view_prompt",
|
||||
"authentik_stages_prompt.view_promptstage",
|
||||
"authentik_stages_redirect.add_redirectstage",
|
||||
"authentik_stages_redirect.change_redirectstage",
|
||||
"authentik_stages_redirect.delete_redirectstage",
|
||||
"authentik_stages_redirect.view_redirectstage",
|
||||
"authentik_stages_source.add_sourcestage",
|
||||
"authentik_stages_source.change_sourcestage",
|
||||
"authentik_stages_source.delete_sourcestage",
|
||||
|
12
go.mod
12
go.mod
@ -9,7 +9,7 @@ require (
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/getsentry/sentry-go v0.30.0
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/go-ldap/ldap/v3 v3.4.9
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -29,7 +29,7 @@ require (
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2024105.1
|
||||
goauthentik.io/api/v3 v3.2024105.3
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sync v0.10.0
|
||||
@ -45,7 +45,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||
@ -76,9 +76,9 @@ require (
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
53
go.sum
53
go.sum
@ -71,8 +71,8 @@ github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBd
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
|
||||
github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@ -86,8 +86,8 @@ github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9
|
||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
||||
github.com/go-ldap/ldap/v3 v3.4.9 h1:KxX9eO44/MpqPXVVMPJDB+k/35GEePHE/Jfvl7oRMUo=
|
||||
github.com/go-ldap/ldap/v3 v3.4.9/go.mod h1:+CE/4PPOOdEPGTi2B7qXKQOq+pNBvXZtlBNcVZY0AWI=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2024105.1 h1:PxOlStLdM+L80ciVJUWZRhf2VQrDVnNMcv+exeQ/qUA=
|
||||
goauthentik.io/api/v3 v3.2024105.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2024105.3 h1:Vl1vwPkCtA8hChsxwO3NUI8nupFC7r93jUHvqM+kYVw=
|
||||
goauthentik.io/api/v3 v3.2024105.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -309,10 +309,12 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -347,6 +349,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -378,10 +383,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -400,6 +406,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -435,16 +444,20 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -453,9 +466,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -501,6 +516,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
File diff suppressed because it is too large
Load Diff
112
poetry.lock
generated
112
poetry.lock
generated
@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
@ -1201,7 +1201,6 @@ files = [
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
|
||||
{file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
|
||||
@ -1212,7 +1211,6 @@ files = [
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
|
||||
{file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
|
||||
@ -1273,13 +1271,37 @@ tests = ["django", "hypothesis", "pytest", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "debugpy"
|
||||
version = "1.8.10"
|
||||
version = "1.8.11"
|
||||
description = "An implementation of the Debug Adapter Protocol for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "debugpy-1.8.10-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97aa00af95983887806e06f37e144909d35215d66db74f8b0e9799b4eef40cfd"},
|
||||
{file = "debugpy-1.8.10.tar.gz", hash = "sha256:ee4ed903cbeb14ee1839549f953af519ffa512598ec987b2051f9c868e2249a8"},
|
||||
{file = "debugpy-1.8.11-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd"},
|
||||
{file = "debugpy-1.8.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f"},
|
||||
{file = "debugpy-1.8.11-cp310-cp310-win32.whl", hash = "sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737"},
|
||||
{file = "debugpy-1.8.11-cp310-cp310-win_amd64.whl", hash = "sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1"},
|
||||
{file = "debugpy-1.8.11-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296"},
|
||||
{file = "debugpy-1.8.11-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1"},
|
||||
{file = "debugpy-1.8.11-cp311-cp311-win32.whl", hash = "sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9"},
|
||||
{file = "debugpy-1.8.11-cp311-cp311-win_amd64.whl", hash = "sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e"},
|
||||
{file = "debugpy-1.8.11-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308"},
|
||||
{file = "debugpy-1.8.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768"},
|
||||
{file = "debugpy-1.8.11-cp312-cp312-win32.whl", hash = "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b"},
|
||||
{file = "debugpy-1.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1"},
|
||||
{file = "debugpy-1.8.11-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3"},
|
||||
{file = "debugpy-1.8.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e"},
|
||||
{file = "debugpy-1.8.11-cp313-cp313-win32.whl", hash = "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28"},
|
||||
{file = "debugpy-1.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1"},
|
||||
{file = "debugpy-1.8.11-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db"},
|
||||
{file = "debugpy-1.8.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0"},
|
||||
{file = "debugpy-1.8.11-cp38-cp38-win32.whl", hash = "sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280"},
|
||||
{file = "debugpy-1.8.11-cp38-cp38-win_amd64.whl", hash = "sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5"},
|
||||
{file = "debugpy-1.8.11-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458"},
|
||||
{file = "debugpy-1.8.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851"},
|
||||
{file = "debugpy-1.8.11-cp39-cp39-win32.whl", hash = "sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7"},
|
||||
{file = "debugpy-1.8.11-cp39-cp39-win_amd64.whl", hash = "sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0"},
|
||||
{file = "debugpy-1.8.11-py2.py3-none-any.whl", hash = "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920"},
|
||||
{file = "debugpy-1.8.11.tar.gz", hash = "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4236,48 +4258,48 @@ cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-kadmin-rs"
|
||||
version = "0.4.0"
|
||||
version = "0.5.2"
|
||||
description = "Python interface to the Kerberos administration interface (kadm5)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "python_kadmin_rs-0.4.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b5a5abda2c60961c1d456c920dd3a3053e615a6f1f5703606953be8dfdddef2a"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:57004e7aa52d95a76b0c6d920526f68b45206c51d8d8520d94511727c7ccbad0"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:66f01443b6376494f67d727663600a413a701852a60c724a3cd728758455f59c"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:2af5a50554753ba62ebc979b7767b43e072cff5b56dc0a1f09970fa9105cf55a"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1720b3b9dc156be08e36b7f3492431d2b475b3ecbfa403d73d6e1fcc5ac70bc4"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66a64d615d28dbf17ad8822d75f6a4685f7db7ddef9ad9d69053dcfab592e4ec"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:56ce2b57fbb3b0e7d0e69bd9ce3e7a165ed018ac4c4d60b259f50e68a6a3bb00"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:1aabecd407afd70fca21208f35ea6d2101fb27922e96c5ceed7fcaa6c44359b0"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e53eb9914eb6542618ec5da67c51e943eb724f76f186d88ae591bd8fde01345a"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c51115155ff1001ab3a0826a3de753927ea1373828e5432bc0eede4ec88c5c72"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:547223f156baa8ef1236c7b3a55bc13506beada6147679f4a73dd1de5e809d30"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:951ca2b9b3019cf82c5e1882d1cec6e28bbf2d900d2b8022aac23a3e65a4ca7d"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b1c1a0b63ec3bd1a023feb094e1c6a93202237416d0783d4677be2b858fe6154"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b27c16187dd24b820c966f03f889c140d0a55f547158fdc5bc2ecb4eb7e94fbe"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fab810574fd54b715806104400a5c105879005597bc043469d506cb8e1e633c4"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:377ffa81264b115fafd2b4a83aab990a138a3684b90a133bc3a6c4081829c358"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b9a3909592404ac0483b3a5d584466198b5e17e370be3e221ff19c4cec97ce4"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e6050fdaa638585046b8579867d3540f99efbf24dc10715ac05bae6ca9bbbffd"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e36c868960619ed4df0e69f53ff9458f661c1a5fbc627554cc7777231e9e69bd"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3f7692eb90ddacc353a5ed3d53fe0bc62df4132b30158e1c9a2bf24340a6929a"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7a551f8010e47413513cc19e0001dfed9178f5de509c4590b02584e0387df55c"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e8623866a0324823af5edc2da6a6e90cb8a0d2ecbeb80f9a04014cc18f1c182f"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:223807b9108723d4f47d3243f6256f4026be0ea7ccbb356807d97a469a8bc628"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e7b8f6a2b183c862b94462251537d508332c82d2c4dec1699875245041c4a684"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:dee6325628edc33eaf217268b521b0923f519fdb7f5ac81dcfb97c9574fb3599"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:59b5db1d0381fbfb0b9ff2f79949abae6c645ccbd7c8c72a9b932fc0eab1d9b0"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:45fd65c49e0c64968d11eb7f6b93a9a09788967ca667e554f35fea467ea67f1e"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:fc49b75be7d032f5a37a53b777267b81070220b9d14777374f159c5b1f64686a"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fafb2e57fbc82e27c26c5450669846e02afbf6b4065127c4396fa2c21ec31c42"},
|
||||
{file = "python_kadmin_rs-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:59b622b7396922748b2463ad0a682b6f6a6887f3eee720eeda8a57bed6370555"},
|
||||
{file = "python_kadmin_rs-0.4.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:40ce8ff6dcc1bd82f34e7aca611e299a4ca51f28e5cb8772ba7d38532c9564d9"},
|
||||
{file = "python_kadmin_rs-0.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ef8030f282bdcbb2a771699e238b0f555336ddd626d4562ef3e2e17abc31c3b"},
|
||||
{file = "python_kadmin_rs-0.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:617292305b3fde5e6b009b70ae8fd6cc5c7a962732558cffbdc27fca157ce574"},
|
||||
{file = "python_kadmin_rs-0.4.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:6fe1d0a03d0a0a75296902ad95b6639372eed93d16422f33572d23b0b144ce64"},
|
||||
{file = "python_kadmin_rs-0.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7fef94fe96687b9c6eb9bf670afda91f24c62fb2bc2f80394e0f2f31474494a5"},
|
||||
{file = "python_kadmin_rs-0.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:627dff4aa5c222fc83cbb5312362bf80c68b515afd7027b27d763916e0adeb39"},
|
||||
{file = "python_kadmin_rs-0.4.0.tar.gz", hash = "sha256:d32befeaa68dbaac077b565f5a47a23cba6e142190c0d521e595b30de8587efa"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1399e507467881882275eb822caee73f7eb509d25c25af406e91a75221a08ec9"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:86c5f0c799ea903fcc7d67ed47ce9080ea639c8468483c4d6e3a854ab268c959"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:67e8805cbdc75e9d0a88378f30acf0bed34fcca5d2130c4d6a613e57676123a7"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:244fca7d8ca7793729b8a01ae9f2a3c5931fca6bc11d7f3b67fa95297146cd8e"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:034bed577e20cdf4682f4d591ec68d51a44e85a101f2d905c3728143390d93f1"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:048e73490278f74510ac7f19a11ca7860c88863f55f2c79a47c875fc174bb2aa"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:dc580a38397dcdd2021127861c0d35a0c85e556644673387e40331f3fb402dc6"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:abb4df1a35bb177a7a9d2aee82d99d2285240368e6a1784c5066003872374679"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:22fd617f126f5dae2e17c4770cea8ffeca7196885508d922798bbdc9368606f1"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:83574caf964140e87df04a1d97d84b1dd1d60395cae430429b8c1b78a1f5e6de"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:01fc8c3cf707bbe011610107a6803ea2cb9025f4152931f40a39dc8b8d29d42a"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bb5091dbeb0159f95292768b5dc7cce057a29339d5f9c085921a8f16baa3cb32"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:0ece4d210b70f7810a8d909f32872bb47602f8c9ca00289fb8d34a6ee79f5b19"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:d351b5793d8340e9101bdd2684dc6e84156e37af910140530e762d2d92905819"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:13e13c0487dfb9f6986fc6a11e8526875c935aa9bbdf9514049f2c5b5b5cdae7"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:40fd1663c47bcada61e0bb7c681a1518b9fd1d17f03e3193bdfb6313e5afa6d0"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:945a9314e47d930825e46f532341ea1f595a7a78a9d75866e5564bd28cd4b6af"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:40cc14b24028a23a796fa5a53e6236c72c90247be803c6a8976f6e758b377f67"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:cd5b032fb5c8d609d38bc417e1e5405885d153d39742bbac6514af28b8930a74"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6f904a912ea04cd285b0d33107d6e68c904b046fa5bd7555c48986ee4ef139f7"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4234bc17dff770cbc32c14b22659651f4c9a882086cc19be7467f4755357f756"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bb3abbf9a0a91a9205cef8ff4fb45bdeb7ee773d2eda67e3a8c01a2f9f561b7f"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6503feef30cb59fd79b573cde5a2e9f892e5b89ffdb78e78db21815f67a14b80"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6bfe54524573ccf4424344af88e57804399061f16aaf2db1965cafce79f3c76"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:c953f2cdfd92217d8ae4d3dc0374305ed0bd21cbfa7de50c5f7dfc53c44eaa7a"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:2632b02116651a23e3b5b7fce87f939067918f41b9d542af21ee09d964d41bfd"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8b8a4a042179e3682a826a5c4bc6ee39055c6133d13d5415d6be2bb0e1d79e4a"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ed6eafd0f9606d1d554aae7b9f5ebae681ef0dc33b08b0affb363fa65b367ad6"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:81df51e55e45fee08890f85230a33ddb066a7116ef8bdbe9ce854f3b95ed4c2d"},
|
||||
{file = "python_kadmin_rs-0.5.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb0954ff796e2cb5813665575ecd8f51df28dfeb52a81601516b056288418a94"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:6fcb5f5c49e96e8ec6c5096c701871978bd2a3a7ef4ebdcbc3abb6a05aa8a5b7"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8437ccce96206c26eb877ddfd7f14c8d8fec0a7cc9344e7dbf982637cd4c28ac"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:beb2619c27b2f079d7d0c67f3e998712f236808f0c2c0a5389f07d1977246762"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cecaeebe7acf78e17730b1fa8e5be7aae0e9052c347fc35b1a2d3f77fd69bfe1"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e14f3ba4017b8266f6db31aba4bf931593373b9ea8a17b5f9cc05cd2e3674a8b"},
|
||||
{file = "python_kadmin_rs-0.5.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9b2692f6e07461703ac1d20c590ffd5e980d918cdb19c95d875e5f1cf1df397"},
|
||||
{file = "python_kadmin_rs-0.5.2.tar.gz", hash = "sha256:8ff0c8cc8f2a10ce20ae0cf1dd5b2d5569e47d1d54cf53c4fbc95f9120e91bd8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5912,4 +5934,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "~3.12"
|
||||
content-hash = "f6e8316415a23b165130d63a7ea311b257f65f5478ad85b0d38ac72fb89bc1c4"
|
||||
content-hash = "38089ad25be7638c118f4b503ad2f8495c941667f5485efe60b2bbdb14d6f44c"
|
||||
|
@ -131,7 +131,7 @@ pydantic-scim = "*"
|
||||
pyjwt = "*"
|
||||
pyrad = "*"
|
||||
python = "~3.12"
|
||||
python-kadmin-rs = "0.4.0"
|
||||
python-kadmin-rs = "0.5.2"
|
||||
pyyaml = "*"
|
||||
requests-oauthlib = "*"
|
||||
scim2-filter-parser = "*"
|
||||
|
429
schema.yml
429
schema.yml
@ -23390,6 +23390,7 @@ paths:
|
||||
- authentik_stages_password.passwordstage
|
||||
- authentik_stages_prompt.prompt
|
||||
- authentik_stages_prompt.promptstage
|
||||
- authentik_stages_redirect.redirectstage
|
||||
- authentik_stages_source.sourcestage
|
||||
- authentik_stages_user_delete.userdeletestage
|
||||
- authentik_stages_user_login.userloginstage
|
||||
@ -23629,6 +23630,7 @@ paths:
|
||||
- authentik_stages_password.passwordstage
|
||||
- authentik_stages_prompt.prompt
|
||||
- authentik_stages_prompt.promptstage
|
||||
- authentik_stages_redirect.redirectstage
|
||||
- authentik_stages_source.sourcestage
|
||||
- authentik_stages_user_delete.userdeletestage
|
||||
- authentik_stages_user_login.userloginstage
|
||||
@ -25867,6 +25869,17 @@ paths:
|
||||
name: enabled
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: kadmin_type
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- Heimdal
|
||||
- MIT
|
||||
- other
|
||||
description: |+
|
||||
KAdmin server type
|
||||
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
@ -35636,6 +35649,275 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/stages/redirect/:
|
||||
get:
|
||||
operationId: stages_redirect_list
|
||||
description: RedirectStage 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
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- stages
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedRedirectStageList'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
post:
|
||||
operationId: stages_redirect_create
|
||||
description: RedirectStage Viewset
|
||||
tags:
|
||||
- stages
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedirectStageRequest'
|
||||
required: true
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedirectStage'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
/stages/redirect/{stage_uuid}/:
|
||||
get:
|
||||
operationId: stages_redirect_retrieve
|
||||
description: RedirectStage Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: stage_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Redirect Stage.
|
||||
required: true
|
||||
tags:
|
||||
- stages
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedirectStage'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
put:
|
||||
operationId: stages_redirect_update
|
||||
description: RedirectStage Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: stage_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Redirect Stage.
|
||||
required: true
|
||||
tags:
|
||||
- stages
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedirectStageRequest'
|
||||
required: true
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedirectStage'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
patch:
|
||||
operationId: stages_redirect_partial_update
|
||||
description: RedirectStage Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: stage_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Redirect Stage.
|
||||
required: true
|
||||
tags:
|
||||
- stages
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedRedirectStageRequest'
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RedirectStage'
|
||||
description: ''
|
||||
'400':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
description: ''
|
||||
'403':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
description: ''
|
||||
delete:
|
||||
operationId: stages_redirect_destroy
|
||||
description: RedirectStage Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: stage_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Redirect 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/redirect/{stage_uuid}/used_by/:
|
||||
get:
|
||||
operationId: stages_redirect_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 Redirect 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/source/:
|
||||
get:
|
||||
operationId: stages_source_list
|
||||
@ -37667,6 +37949,7 @@ components:
|
||||
- authentik.stages.invitation
|
||||
- authentik.stages.password
|
||||
- authentik.stages.prompt
|
||||
- authentik.stages.redirect
|
||||
- authentik.stages.user_delete
|
||||
- authentik.stages.user_login
|
||||
- authentik.stages.user_logout
|
||||
@ -37979,6 +38262,7 @@ components:
|
||||
- require_authenticated
|
||||
- require_unauthenticated
|
||||
- require_superuser
|
||||
- require_redirect
|
||||
- require_outpost
|
||||
type: string
|
||||
AuthenticatorAttachmentEnum:
|
||||
@ -41420,6 +41704,7 @@ components:
|
||||
- $ref: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/PromptChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/RedirectChallengeResponseRequest'
|
||||
- $ref: '#/components/schemas/UserLoginChallengeResponseRequest'
|
||||
discriminator:
|
||||
propertyName: component
|
||||
@ -41443,6 +41728,7 @@ components:
|
||||
ak-stage-password: '#/components/schemas/PasswordChallengeResponseRequest'
|
||||
ak-source-plex: '#/components/schemas/PlexAuthenticationChallengeResponseRequest'
|
||||
ak-stage-prompt: '#/components/schemas/PromptChallengeResponseRequest'
|
||||
xak-flow-redirect: '#/components/schemas/RedirectChallengeResponseRequest'
|
||||
ak-stage-user-login: '#/components/schemas/UserLoginChallengeResponseRequest'
|
||||
FlowDesignationEnum:
|
||||
enum:
|
||||
@ -42879,6 +43165,12 @@ components:
|
||||
- global
|
||||
- per_provider
|
||||
type: string
|
||||
KadminTypeEnum:
|
||||
enum:
|
||||
- MIT
|
||||
- Heimdal
|
||||
- other
|
||||
type: string
|
||||
KerberosSource:
|
||||
type: object
|
||||
description: Kerberos Source Serializer
|
||||
@ -42966,6 +43258,10 @@ components:
|
||||
krb5_conf:
|
||||
type: string
|
||||
description: Custom krb5.conf to use. Uses the system one by default
|
||||
kadmin_type:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/KadminTypeEnum'
|
||||
description: KAdmin server type
|
||||
sync_users:
|
||||
type: boolean
|
||||
description: Sync users from Kerberos into authentik
|
||||
@ -43134,6 +43430,10 @@ components:
|
||||
krb5_conf:
|
||||
type: string
|
||||
description: Custom krb5.conf to use. Uses the system one by default
|
||||
kadmin_type:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/KadminTypeEnum'
|
||||
description: KAdmin server type
|
||||
sync_users:
|
||||
type: boolean
|
||||
description: Sync users from Kerberos into authentik
|
||||
@ -44422,6 +44722,7 @@ components:
|
||||
- authentik_stages_password.passwordstage
|
||||
- authentik_stages_prompt.prompt
|
||||
- authentik_stages_prompt.promptstage
|
||||
- authentik_stages_redirect.redirectstage
|
||||
- authentik_stages_user_delete.userdeletestage
|
||||
- authentik_stages_user_login.userloginstage
|
||||
- authentik_stages_user_logout.userlogoutstage
|
||||
@ -46519,6 +46820,18 @@ components:
|
||||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedRedirectStageList:
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
$ref: '#/components/schemas/Pagination'
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RedirectStage'
|
||||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedReputationList:
|
||||
type: object
|
||||
properties:
|
||||
@ -48459,6 +48772,10 @@ components:
|
||||
krb5_conf:
|
||||
type: string
|
||||
description: Custom krb5.conf to use. Uses the system one by default
|
||||
kadmin_type:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/KadminTypeEnum'
|
||||
description: KAdmin server type
|
||||
sync_users:
|
||||
type: boolean
|
||||
description: Sync users from Kerberos into authentik
|
||||
@ -49582,6 +49899,27 @@ components:
|
||||
should only be enabled if all users that will bind to this provider have
|
||||
a TOTP device configured, as otherwise a password may incorrectly be rejected
|
||||
if it contains a semicolon.
|
||||
PatchedRedirectStageRequest:
|
||||
type: object
|
||||
description: RedirectStage Serializer
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
flow_set:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FlowSetRequest'
|
||||
keep_context:
|
||||
type: boolean
|
||||
mode:
|
||||
$ref: '#/components/schemas/RedirectStageModeEnum'
|
||||
target_static:
|
||||
type: string
|
||||
target_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
PatchedReputationPolicyRequest:
|
||||
type: object
|
||||
description: Reputation Policy Serializer
|
||||
@ -52120,6 +52458,97 @@ components:
|
||||
type: string
|
||||
required:
|
||||
- to
|
||||
RedirectChallengeResponseRequest:
|
||||
type: object
|
||||
description: Redirect challenge response
|
||||
properties:
|
||||
component:
|
||||
type: string
|
||||
minLength: 1
|
||||
default: xak-flow-redirect
|
||||
to:
|
||||
type: string
|
||||
minLength: 1
|
||||
required:
|
||||
- to
|
||||
RedirectStage:
|
||||
type: object
|
||||
description: RedirectStage 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'
|
||||
keep_context:
|
||||
type: boolean
|
||||
mode:
|
||||
$ref: '#/components/schemas/RedirectStageModeEnum'
|
||||
target_static:
|
||||
type: string
|
||||
target_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
required:
|
||||
- component
|
||||
- meta_model_name
|
||||
- mode
|
||||
- name
|
||||
- pk
|
||||
- verbose_name
|
||||
- verbose_name_plural
|
||||
RedirectStageModeEnum:
|
||||
enum:
|
||||
- static
|
||||
- flow
|
||||
type: string
|
||||
RedirectStageRequest:
|
||||
type: object
|
||||
description: RedirectStage Serializer
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
flow_set:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FlowSetRequest'
|
||||
keep_context:
|
||||
type: boolean
|
||||
mode:
|
||||
$ref: '#/components/schemas/RedirectStageModeEnum'
|
||||
target_static:
|
||||
type: string
|
||||
target_flow:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
required:
|
||||
- mode
|
||||
- name
|
||||
RedirectURI:
|
||||
type: object
|
||||
description: A single allowed redirect URI entry
|
||||
|
8
web/package-lock.json
generated
8
web/package-lock.json
generated
@ -23,7 +23,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2024.10.5-1733854821",
|
||||
"@goauthentik/api": "^2024.10.5-1734022840",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
@ -1775,9 +1775,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2024.10.5-1733854821",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.10.5-1733854821.tgz",
|
||||
"integrity": "sha512-WLZxl56j0b007b/Mv3xhWZS5oWCOeCDnn1fq4I3xN/Esm76uuAOizXN/Cpv6QMLzcwoMKTE6d5L4GUDIy3jwZQ=="
|
||||
"version": "2024.10.5-1734022840",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.10.5-1734022840.tgz",
|
||||
"integrity": "sha512-scVh/WyDMPvYkJt1DEZY1EbEWyUGCpISo2PUIQmZgSGF0opMBafwLPMT+LFJIm0hEmGiJ4leyn4BIfKsQKV+mg=="
|
||||
},
|
||||
"node_modules/@goauthentik/web": {
|
||||
"resolved": "",
|
||||
|
@ -11,7 +11,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2024.10.5-1733854821",
|
||||
"@goauthentik/api": "^2024.10.5-1734022840",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
|
@ -189,21 +189,28 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
?selected=${this.instance?.authentication ===
|
||||
AuthenticationEnum.RequireUnauthenticated}
|
||||
>
|
||||
${msg("Require no authentication.")}
|
||||
${msg("Require no authentication")}
|
||||
</option>
|
||||
<option
|
||||
value=${AuthenticationEnum.RequireSuperuser}
|
||||
?selected=${this.instance?.authentication ===
|
||||
AuthenticationEnum.RequireSuperuser}
|
||||
>
|
||||
${msg("Require superuser.")}
|
||||
${msg("Require superuser")}
|
||||
</option>
|
||||
<option
|
||||
value=${AuthenticationEnum.RequireRedirect}
|
||||
?selected=${this.instance?.authentication ===
|
||||
AuthenticationEnum.RequireRedirect}
|
||||
>
|
||||
${msg("Require being redirected from another flow")}
|
||||
</option>
|
||||
<option
|
||||
value=${AuthenticationEnum.RequireOutpost}
|
||||
?selected=${this.instance?.authentication ===
|
||||
AuthenticationEnum.RequireOutpost}
|
||||
>
|
||||
${msg("Require Outpost (flow can only be executed from an outpost).")}
|
||||
${msg("Require Outpost (flow can only be executed from an outpost)")}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
|
@ -27,6 +27,7 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import {
|
||||
FlowsInstancesListDesignationEnum,
|
||||
GroupMatchingModeEnum,
|
||||
KadminTypeEnum,
|
||||
KerberosSource,
|
||||
KerberosSourceRequest,
|
||||
SourcesApi,
|
||||
@ -215,6 +216,34 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
||||
<ak-form-group .expanded=${false}>
|
||||
<span slot="header"> ${msg("Sync connection settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("KAdmin type")}
|
||||
?required=${true}
|
||||
name="kadminType"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: "MIT",
|
||||
value: KadminTypeEnum.Mit,
|
||||
default: true,
|
||||
description: html`${msg("MIT krb5 kadmin")}`,
|
||||
},
|
||||
{
|
||||
label: "Heimdal",
|
||||
value: KadminTypeEnum.Heimdal,
|
||||
description: html`${msg("Heimdal kadmin")}`,
|
||||
},
|
||||
{
|
||||
label: msg("Other"),
|
||||
value: KadminTypeEnum.Other,
|
||||
description: html`${msg("Other type of kadmin")}`,
|
||||
},
|
||||
]}
|
||||
.value=${this.instance?.kadminType}
|
||||
>
|
||||
</ak-radio>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-text-input
|
||||
name="syncPrincipal"
|
||||
label=${msg("Sync principal")}
|
||||
|
@ -17,6 +17,7 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
|
||||
import "@goauthentik/admin/stages/invitation/InvitationStageForm";
|
||||
import "@goauthentik/admin/stages/password/PasswordStageForm";
|
||||
import "@goauthentik/admin/stages/prompt/PromptStageForm";
|
||||
import "@goauthentik/admin/stages/redirect/RedirectStageForm";
|
||||
import "@goauthentik/admin/stages/source/SourceStageForm";
|
||||
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
|
||||
import "@goauthentik/admin/stages/user_login/UserLoginStageForm";
|
||||
|
@ -15,6 +15,7 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
|
||||
import "@goauthentik/admin/stages/invitation/InvitationStageForm";
|
||||
import "@goauthentik/admin/stages/password/PasswordStageForm";
|
||||
import "@goauthentik/admin/stages/prompt/PromptStageForm";
|
||||
import "@goauthentik/admin/stages/redirect/RedirectStageForm";
|
||||
import "@goauthentik/admin/stages/source/SourceStageForm";
|
||||
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
|
||||
import "@goauthentik/admin/stages/user_login/UserLoginStageForm";
|
||||
|
@ -83,13 +83,13 @@ export class ConsentStageForm extends BaseStageForm<ConsentStage> {
|
||||
value=${ConsentStageModeEnum.Permanent}
|
||||
?selected=${this.instance?.mode === ConsentStageModeEnum.Permanent}
|
||||
>
|
||||
${msg("Consent given last indefinitely")}
|
||||
${msg("Consent given lasts indefinitely")}
|
||||
</option>
|
||||
<option
|
||||
value=${ConsentStageModeEnum.Expiring}
|
||||
?selected=${this.instance?.mode === ConsentStageModeEnum.Expiring}
|
||||
>
|
||||
${msg("Consent expires.")}
|
||||
${msg("Consent expires")}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
|
145
web/src/admin/stages/redirect/RedirectStageForm.ts
Normal file
145
web/src/admin/stages/redirect/RedirectStageForm.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import {
|
||||
Flow,
|
||||
FlowsApi,
|
||||
FlowsInstancesListRequest,
|
||||
RedirectStage,
|
||||
RedirectStageModeEnum,
|
||||
StagesApi,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-redirect-form")
|
||||
export class RedirectStageForm extends BaseStageForm<RedirectStage> {
|
||||
@property({ type: String })
|
||||
mode: string = RedirectStageModeEnum.Static;
|
||||
|
||||
loadInstance(pk: string): Promise<RedirectStage> {
|
||||
return new StagesApi(DEFAULT_CONFIG)
|
||||
.stagesRedirectRetrieve({
|
||||
stageUuid: pk,
|
||||
})
|
||||
.then((stage) => {
|
||||
this.mode = stage.mode ?? RedirectStageModeEnum.Static;
|
||||
return stage;
|
||||
});
|
||||
}
|
||||
|
||||
async send(data: RedirectStage): Promise<RedirectStage> {
|
||||
if (this.instance) {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesRedirectUpdate({
|
||||
stageUuid: this.instance.pk || "",
|
||||
redirectStageRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesRedirectCreate({
|
||||
redirectStageRequest: data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<span>
|
||||
${msg("Redirect the user to another flow, potentially with all gathered context")}
|
||||
</span>
|
||||
<ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.name ?? ""}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group expanded>
|
||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
||||
<select
|
||||
class="pf-c-form-control"
|
||||
@change=${(ev: Event) => {
|
||||
const target = ev.target as HTMLSelectElement;
|
||||
this.mode = target.selectedOptions[0].value;
|
||||
}}
|
||||
>
|
||||
<option
|
||||
value=${RedirectStageModeEnum.Static}
|
||||
?selected=${this.instance?.mode === RedirectStageModeEnum.Static}
|
||||
>
|
||||
${msg("Static")}
|
||||
</option>
|
||||
<option
|
||||
value=${RedirectStageModeEnum.Flow}
|
||||
?selected=${this.instance?.mode === RedirectStageModeEnum.Flow}
|
||||
>
|
||||
${msg("Flow")}
|
||||
</option>
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
?hidden=${this.mode !== RedirectStageModeEnum.Static}
|
||||
label=${msg("Target URL")}
|
||||
name="targetStatic"
|
||||
required
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${this.instance?.targetStatic ?? ""}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Redirect the user to a static URL.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
?hidden=${this.mode !== RedirectStageModeEnum.Flow}
|
||||
label=${msg("Target Flow")}
|
||||
name="targetFlow"
|
||||
required
|
||||
>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
||||
const args: FlowsInstancesListRequest = {
|
||||
ordering: "slug",
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
}
|
||||
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(
|
||||
args,
|
||||
);
|
||||
return flows.results;
|
||||
}}
|
||||
.renderElement=${(flow: Flow): string => RenderFlowOption(flow)}
|
||||
.renderDescription=${(flow: Flow): TemplateResult => html`${flow.name}`}
|
||||
.value=${(flow: Flow | undefined): string | undefined => flow?.pk}
|
||||
.selected=${(flow: Flow): boolean =>
|
||||
this.instance?.targetFlow === flow.pk}
|
||||
blankable
|
||||
>
|
||||
</ak-search-select>
|
||||
<p class="pf-c-form__helper-text">${msg("Redirect the user to a Flow.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-switch-input
|
||||
?hidden=${this.mode !== RedirectStageModeEnum.Flow}
|
||||
name="keepContext"
|
||||
label=${msg("Keep flow context")}
|
||||
?checked="${this.instance?.keepContext ?? true}"
|
||||
>
|
||||
</ak-switch-input>
|
||||
</div>
|
||||
</ak-form-group>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-stage-redirect-form": RedirectStageForm;
|
||||
}
|
||||
}
|
@ -377,7 +377,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
|
||||
`
|
||||
: html` <p>
|
||||
${msg(
|
||||
"To let a user directly reset a their password, configure a recovery flow on the currently active brand.",
|
||||
"To let a user directly reset their password, configure a recovery flow on the currently active brand.",
|
||||
)}
|
||||
</p>`}
|
||||
</div>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 671 KiB After Width: | Height: | Size: 772 KiB |
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
|
||||
<body>
|
||||
<trans-unit id="s4caed5b7a7e5d89b">
|
||||
@ -596,9 +596,9 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="saa0e2675da69651b">
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>未找到 URL "
|
||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>未找到 URL "
|
||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s58cd9c2fe836d9c6">
|
||||
@ -1737,8 +1737,8 @@
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa90b7809586c35ce">
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s0410779cb47de312">
|
||||
@ -2901,8 +2901,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s76768bebabb7d543">
|
||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
|
||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s026555347e589f0e">
|
||||
@ -3648,8 +3648,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s7b1fba26d245cb1c">
|
||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s44536d20bb5c8257">
|
||||
@ -3825,10 +3825,10 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa95a538bfbb86111">
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<target>您确定要更新
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sc92d7cfb6ee1fec6">
|
||||
@ -4904,7 +4904,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sdf1d8edef27236f0">
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
||||
|
||||
</trans-unit>
|
||||
@ -5283,7 +5283,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s1608b2f94fa0dbd4">
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target>
|
||||
|
||||
</trans-unit>
|
||||
@ -7694,7 +7694,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>成功创建用户并添加到组 <x id="0" equiv-text="${this.group.name}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s824e0943a7104668">
|
||||
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
|
||||
<target>此用户将会被添加到组 &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s62e7f6ed7d9cb3ca">
|
||||
@ -9044,8 +9044,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>同步组</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2d5f69929bb7221d">
|
||||
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${prompt.name}"/>(&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;,类型为 <x id="2" equiv-text="${prompt.type}"/>)</target>
|
||||
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${p.name}"/>(&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;,类型为 <x id="2" equiv-text="${p.type}"/>)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa38c5a2731be3a46">
|
||||
<source>authentik was unable to save this application:</source>
|
||||
@ -9296,8 +9296,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>授权流程成功后有效的重定向 URI。还可以在此处为隐式流程指定任何来源。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4c49d27de60a532b">
|
||||
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>要允许任何重定向 URI,请设置模式为正则表达式,并将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>要允许任何重定向 URI,请设置模式为正则表达式,并将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s43f899a86c6a3484">
|
||||
<source>Redirect URIs/Origins</source>
|
||||
@ -9305,19 +9305,24 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa52bf79fe1ccb13e">
|
||||
<source>Federated OIDC Sources</source>
|
||||
<target>联邦式 OIDC 源</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s555465bf6577505e">
|
||||
<source>Federated OIDC Providers</source>
|
||||
<target>联邦式 OIDC 提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s066bfb12c9032dc2">
|
||||
<source>Available Providers</source>
|
||||
<target>可用提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa12111ca3f3e398e">
|
||||
<source>Selected Providers</source>
|
||||
<target>已选提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f8a3f7792e6b940">
|
||||
<source>JWTs signed by the selected providers can be used to authenticate to this provider.</source>
|
||||
<target>由已选提供程序签发的 JWT 可以用于此提供程序的身份验证。</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
@ -9044,8 +9044,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<target>同步组</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2d5f69929bb7221d">
|
||||
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${prompt.name}"/>(&quot;<x id="1" equiv-text="${prompt.fieldKey}"/>&quot;,类型为 <x id="2" equiv-text="${prompt.type}"/>)</target>
|
||||
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
|
||||
<target><x id="0" equiv-text="${p.name}"/>(&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;,类型为 <x id="2" equiv-text="${p.type}"/>)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa38c5a2731be3a46">
|
||||
<source>authentik was unable to save this application:</source>
|
||||
@ -9302,6 +9302,26 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s43f899a86c6a3484">
|
||||
<source>Redirect URIs/Origins</source>
|
||||
<target>重定向 URI/Origin</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa52bf79fe1ccb13e">
|
||||
<source>Federated OIDC Sources</source>
|
||||
<target>联邦式 OIDC 源</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s555465bf6577505e">
|
||||
<source>Federated OIDC Providers</source>
|
||||
<target>联邦式 OIDC 提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s066bfb12c9032dc2">
|
||||
<source>Available Providers</source>
|
||||
<target>可用提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sa12111ca3f3e398e">
|
||||
<source>Selected Providers</source>
|
||||
<target>已选提供程序</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4f8a3f7792e6b940">
|
||||
<source>JWTs signed by the selected providers can be used to authenticate to this provider.</source>
|
||||
<target>由已选提供程序签发的 JWT 可以用于此提供程序的身份验证。</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
@ -12,6 +12,8 @@ For example, in the Identification Stage (part of the default login flow), you c
|
||||
|
||||
Any data can be stored in the flow context, however there are some reserved keys in the context dictionary that are used by authentik stages.
|
||||
|
||||
To manage flow context on a more granular level, see [Setting flow context keys](../../../../customize/policies/expression/managing_flow_context_keys.md).
|
||||
|
||||
## Context dictionary and reserved keys
|
||||
|
||||
This section describes the data (the context) that are used in authentik, and provides a list of keys, what they are used for and when they are set.
|
||||
@ -68,11 +70,15 @@ When a flow is executed by an Outpost (for example the [LDAP](../../../providers
|
||||
|
||||
#### `is_sso` (boolean)
|
||||
|
||||
Set to `True` when the flow is executed from an "SSO" context. For example, this is set when a flow is used during the authentication or enrollment via an external source, and if a flow is executed to authorize access to an application.
|
||||
This key is set to `True` when the flow is executed from an "SSO" context. For example, this is set when a flow is used during the authentication or enrollment via an external source, and if a flow is executed to authorize access to an application.
|
||||
|
||||
#### `is_restored` (Token object)
|
||||
|
||||
Set when a flow execution is continued from a token. This happens for example when an [Email stage](../../stages/email/index.mdx) is used and the user clicks on the link within the email. The token object contains the key that was used to restore the flow execution.
|
||||
This key is set when a flow execution is continued from a token. This happens for example when an [Email stage](../../stages/email/index.mdx) is used and the user clicks on the link within the email. The token object contains the key that was used to restore the flow execution.
|
||||
|
||||
#### `is_redirected` (Flow object) <span class="badge badge--version">authentik 2024.12+</span>
|
||||
|
||||
This key is set when the current flow was reached through a [Redirect stage](../../stages/redirect/index.md) in Flow mode.
|
||||
|
||||
### Stage-specific keys
|
||||
|
||||
@ -189,3 +195,11 @@ Optionally override the email address that the email will be sent to. If not set
|
||||
##### `pending_user_identifier` (string)
|
||||
|
||||
If _Show matched user_ is disabled, this key will be set to the user identifier entered by the user in the identification stage.
|
||||
|
||||
#### Redirect stage
|
||||
|
||||
##### `redirect_stage_target` (string) <span class="badge badge--version">authentik 2024.12+</span>
|
||||
|
||||
[Set this key](../../../../customize/policies/expression/managing_flow_context_keys.md) in an Expression Policy to override [Redirect stage](../../stages/redirect/index.md) to force it to redirect to a certain URL or flow. This is useful when a flow requires that the redirection target be decided dynamically.
|
||||
|
||||
Use the format `ak-flow://{slug}` to use the Redirect stage in Flow mode. Any other format will result in the Redirect stage running in Static mode.
|
||||
|
@ -44,7 +44,7 @@ To create a flow, follow these steps:
|
||||
2. In the Admin interface, navigate to **Flows and Stages -> Flows**.
|
||||
3. Click **Create**, define the flow using the [configuration settings](#flow-configuration-options) described below, and then click **Finish**.
|
||||
|
||||
After creating the flow, you can then [bind specific stages](../stages/index.md#bind-a-stage-to-a-flow) to the flow and [bind policies](../../../customize/policies/working_with_policies/working_with_policies.md) to the flow to further customize the user's log in and authentication process.
|
||||
After creating the flow, you can then [bind specific stages](../stages/index.md#bind-a-stage-to-a-flow) to the flow and [bind policies](../../../customize/policies/working_with_policies.md) to the flow to further customize the user's log in and authentication process.
|
||||
|
||||
To determine which flow should be used, authentik will first check which default authentication flow is configured in the active [**Brand**](../../../customize/brands.md). If no default is configured there, the policies in all flows with the matching designation are checked, and the first flow with matching policies sorted by `slug` will be used.
|
||||
|
||||
@ -66,7 +66,7 @@ import Defaultflowlist from "../flow/flow_list/\_defaultflowlist.mdx";
|
||||
|
||||
<Defaultflowlist />
|
||||
|
||||
**Authentication**: Using this option, you can configure whether the the flow requires initial authentication or not, whether the user must be a superuser, or if the flow requires an outpost.
|
||||
**Authentication**: Using this option, you can configure whether the the flow requires initial authentication or not, whether the user must be a superuser, if the flow can only be started after being redirected by a [Redirect stage](../stages/redirect/index.md), or if the flow requires an outpost.
|
||||
|
||||
**Behavior settings**:
|
||||
|
||||
|
@ -43,7 +43,7 @@ To create a stage, follow these steps:
|
||||
2. In the Admin interface, navigate to **Flows and Stages -> Stages**.
|
||||
3. Click **Create**, define the flow using the configuration settings, and then click **Finish**.
|
||||
|
||||
After creating the stage, you can then [bind the stage to a flow](#bind-a-stage-to-a-flow) or [bind a policy to the stage](../../../customize/policies/working_with_policies/working_with_policies.md) (the policy determines whether or not the stage will be implemented in the flow).
|
||||
After creating the stage, you can then [bind the stage to a flow](#bind-a-stage-to-a-flow) or [bind a policy to the stage](../../../customize/policies/working_with_policies.md) (the policy determines whether or not the stage will be implemented in the flow).
|
||||
|
||||
## Bind a stage to a flow
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Redirect stage
|
||||
---
|
||||
|
||||
<span class="badge badge--version">authentik 2024.12+</span>
|
||||
|
||||
---
|
||||
|
||||
This stage's main purpose is to redirect the user to a new Flow while keeping flow context. For convenience, it can also redirect the user to a static URL.
|
||||
|
||||
## Redirect stage modes
|
||||
|
||||
### Static mode
|
||||
|
||||
When the user reaches this stage, they are redirected to a static URL.
|
||||
|
||||
### Flow mode
|
||||
|
||||
When the user reaches this stage, they are redirected to a specified flow, retaining all [flow context](../../flow/context).
|
||||
|
||||
Optionally, untoggle the "Keep flow context" switch. If this is untoggled, all flow context is cleared with the exception of the [is_redirected](../../flow/context#is_redirected-flow-object-authentik-202412) key.
|
@ -20,7 +20,7 @@ Host: authentik.company
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
client_id=application_client_id&
|
||||
scopes=openid email my-other-scope
|
||||
scope=openid email my-other-scope
|
||||
```
|
||||
|
||||
The response contains the following fields:
|
||||
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Managing flow context keys
|
||||
---
|
||||
|
||||
[Flow context](../../../add-secure-apps/flows-stages/flow/context/index.md) can be managed in [Expression policies](../expression.mdx) via the `context['flow_plan'].context` variable.
|
||||
|
||||
Here's an example of setting a key in an Expression policy:
|
||||
|
||||
```python
|
||||
context['flow_plan'].context['redirect_stage_target'] = 'ak-flow://redirected-authentication-flow'
|
||||
```
|
||||
|
||||
And here's an example of removing that key:
|
||||
|
||||
```python
|
||||
context['flow_plan'].context.pop('redirect_stage_target', None)
|
||||
```
|
@ -8,7 +8,7 @@ In effect, policies determine whether or not a specific stage is applied to a fl
|
||||
|
||||
For example, you can create a policy that, for certain users, skips over a stage that prompts for MFA input. Or, you can define a policy that allows users to access a login flow only if the policy criteria are met. See below for other policies, including the reputation policy and an events-driven policy to manage notifications.
|
||||
|
||||
For instructions about creating and binding policies to flows and stages, refer to ["Working with policies](./working_with_policies/working_with_policies.md)".
|
||||
For instructions about creating and binding policies to flows and stages, refer to ["Working with policies](./working_with_policies.md)".
|
||||
|
||||
## Standard policies
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
title: Working with policies
|
||||
---
|
||||
|
||||
For an overview of policies, refer to our documentation on [Policies](../index.md).
|
||||
For an overview of policies, refer to our documentation on [Policies](./index.md).
|
||||
|
||||
authentik provides several [standard policy types](../index.md#standard-policies), which can be configured for your specific needs.
|
||||
authentik provides several [standard policy types](./index.md#standard-policies), which can be configured for your specific needs.
|
||||
|
||||
We also document how to use a policy to [whitelist email domains](./whitelist_email.md) and to [ensure unique email addresses](./unique_email.md).
|
||||
We also document how to use a policy to [whitelist email domains](./expression/whitelist_email.md) and to [ensure unique email addresses](./expression/unique_email.md).
|
||||
|
||||
## Create a policy
|
||||
|
||||
@ -19,7 +19,7 @@ To create a new policy, follow these steps:
|
||||
|
||||
## Bind a policy to a flow or stage
|
||||
|
||||
After creating the policy, you can bind it to either a [flow](../../../add-secure-apps/flows-stages/flow/index.md) or to a [stage](../../../add-secure-apps/flows-stages/stages/index.md).
|
||||
After creating the policy, you can bind it to either a [flow](../../add-secure-apps/flows-stages/flow/index.md) or to a [stage](../../add-secure-apps/flows-stages/stages/index.md).
|
||||
|
||||
:::info
|
||||
Bindings are instantiated objects themselves, and conceptually can be considered as the "connector" between the policy and the stage or flow. This is why you might read about "binding a binding", because technically, a binding is "spliced" into another binding, in order to intercept and enforce the criteria defined in the policy. You can edit bindings on a flow's **Stage Bindings** tab.
|
@ -15,6 +15,8 @@ If you want to access authentik behind a reverse proxy, there are a few headers
|
||||
|
||||
It is also recommended to use a [modern TLS configuration](https://ssl-config.mozilla.org/) and disable SSL/TLS protocols older than TLS 1.3.
|
||||
|
||||
If your reverse proxy isn't accessing authentik from a private IP address, [trusted proxy CIDRs configuration](./configuration/configuration.mdx#listen-settings) needs to be set on the authentik server to allow client IP address detection.
|
||||
|
||||
The following nginx configuration can be used as a starting point for your own configuration.
|
||||
|
||||
```
|
||||
|
@ -130,6 +130,18 @@ The following variable is available to Kerberos source property mappings:
|
||||
|
||||
- `principal`: a Python string containing the Kerberos principal. For example `alice@REALM.COMPANY` or `HTTP/authentik.company@REALM.COMPANY`.
|
||||
|
||||
When the property mapping is invoked from a SPNEGO context, the following variable is also available:
|
||||
|
||||
- `spnego_info`: a Python dictionary with the following keys:
|
||||
- `initiator_name`: the name of the initiator of the GSSAPI security context
|
||||
- `target_name`: the name of the target of the GSSAPI security context
|
||||
- `mech`: the GSSAPI mechanism used. Should always be Kerberos
|
||||
- `actual_flags`: the flags set on the GSSAPI security context
|
||||
|
||||
When the property mapping is invoked from a synchronization context, the following variable is also available:
|
||||
|
||||
- `principal_obj`: a [`Principal`](https://kadmin-rs.readthedocs.io/latest/kadmin.html#kadmin.Principal) object retrieved from the KAdmin API
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
You can start authentik with the `KRB5_TRACE=/dev/stderr` environment variable for Kerberos to print errors in the logs.
|
||||
|
@ -4,7 +4,7 @@ title: Manage users
|
||||
|
||||
The following topics are for the basic management of users: how to create, modify, delete or deactivate users, and using a recovery email.
|
||||
|
||||
[Policies](../../customize/policies/index.md) can be used to further manage how users are authenticated. For example, by default authentik does not require email addresses be unique, but you can use a policy to [enforce unique email addresses](../../customize/policies/working_with_policies/unique_email.md).
|
||||
[Policies](../../customize/policies/index.md) can be used to further manage how users are authenticated. For example, by default authentik does not require email addresses be unique, but you can use a policy to [enforce unique email addresses](../../customize/policies/expression/unique_email.md).
|
||||
|
||||
### Create a user
|
||||
|
||||
@ -74,21 +74,29 @@ For more information, review ["Permissions"](../access-control/permissions.md).
|
||||
|
||||
If a user has lost their credentials, there are several options.
|
||||
|
||||
### Email them a recovery link
|
||||
### Generate a recovery link
|
||||
|
||||
:::info
|
||||
This option is only available if a default recovery flow was configured for the currently active brand.
|
||||
:::
|
||||
|
||||
1. In the Admin interface, navigate to **Directory > Users** to display all users.
|
||||
2. Either click the name of the user to display the full User details page, or click the chevron (the › symbol) beside their name to expand the options.
|
||||
3. To generate a recovery link, which you can then copy and paste into an email, click **View recovery link**.
|
||||
3. To generate a recovery link, which you can then copy and paste into an email, click **Create recovery link**.
|
||||
|
||||
A pop-up will appear on your browser with the link for you to copy and to send to the user.
|
||||
|
||||
### Automate email to a user
|
||||
### Email them a recovery link
|
||||
|
||||
You can use our automated email to send a link with the URL for the user to reset their password. This option will only work if you have properly [configured a SMTP server during the installation](../../install-config/install/docker-compose.mdx#email-configuration-optional-but-recommended) and set an email address for the user.
|
||||
:::info
|
||||
This option is only available if a default recovery flow was configured for the currently active brand and if the configured flow has an [Email Stage](../../add-secure-apps/flows-stages/stages/email/index.mdx) bound to it.
|
||||
:::
|
||||
|
||||
You can send a link with the URL for the user to reset their password via Email. This option will only work if you have properly [configured a SMTP server during the installation](../../install-config/install/docker-compose.mdx#email-configuration-optional-but-recommended) and set an email address for the user.
|
||||
|
||||
1. In the Admin interface, navigate to **Directory > Users** to display all users.
|
||||
2. Either click the name of the user to display the full User details page, or click the chevron beside their name to expand the toptions.
|
||||
3. To send the automated email to the user, click **Email recovery link**.
|
||||
3. To send the email to the user, click **Email recovery link**.
|
||||
|
||||
If the user does not receive the email, check if the mail server parameters [are properly configured](../../troubleshooting/emails.md).
|
||||
|
||||
|
@ -88,8 +88,63 @@ vault write auth/oidc/role/reader \
|
||||
policies="reader"
|
||||
```
|
||||
|
||||
## External Groups
|
||||
|
||||
If you wish to manage group membership in Hashicorp Vault via Authentik you have to use [external groups](https://developer.hashicorp.com/vault/tutorials/auth-methods/oidc-auth#create-an-external-vault-group).
|
||||
|
||||
:::note
|
||||
If you intend to create [external groups](https://developer.hashicorp.com/vault/tutorials/auth-methods/oidc-auth#create-an-external-vault-group) in Vault to manage user access the OIDC role will need to specifically request a custom scope using the `oidc_scopes` option when creating the OIDC role.
|
||||
This assumes that the steps above have already been completed and tested.
|
||||
:::
|
||||
You should then be able to sign in via OIDC
|
||||
|
||||
### Step 1
|
||||
|
||||
In authentik, edit the OIDC provider created above. Under **Advanced protocol settings** add `authentik default OAuth Mapping: OpenID 'profile'` This includes the groups mapping.
|
||||
|
||||
### Step 2
|
||||
|
||||
In Vault, change the reader role to have the following settings:
|
||||
|
||||
```
|
||||
vault write auth/oidc/role/reader \
|
||||
bound_audiences="Client ID" \
|
||||
allowed_redirect_uris="https://vault.company/ui/vault/auth/oidc/oidc/callback" \
|
||||
allowed_redirect_uris="https://vault.company/oidc/callback" \
|
||||
allowed_redirect_uris="http://localhost:8250/oidc/callback" \
|
||||
user_claim="sub" \
|
||||
policies="reader" \
|
||||
groups_claim="groups" \
|
||||
oidc_scopes=[ "openid profile email" ]
|
||||
```
|
||||
|
||||
Add a group.
|
||||
|
||||
```
|
||||
vault write identity/group/reader \
|
||||
name="reader" \
|
||||
policies=["reader"] \
|
||||
type="external"
|
||||
```
|
||||
|
||||
Get the canonical ID of the group.
|
||||
|
||||
```
|
||||
vault list identity/group/id
|
||||
```
|
||||
|
||||
Get the ID of the OIDC accessor.
|
||||
|
||||
```
|
||||
vault auth list
|
||||
```
|
||||
|
||||
Add a group alias, this maps the group to the OIDC backend.
|
||||
|
||||
```
|
||||
vault write identity/group-alias \
|
||||
mount_accessor="auth_oidc_xxxxxx" \
|
||||
canonical_id="group_id" \
|
||||
name="group name in authentik"
|
||||
```
|
||||
|
||||
You should then be able to sign in via OIDC.
|
||||
`vault login -method=oidc role="reader"`
|
||||
|
56
website/integrations/services/hoarder/index.md
Normal file
56
website/integrations/services/hoarder/index.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Integrate with Hoarder
|
||||
sidebar_label: Hoarder
|
||||
---
|
||||
|
||||
# Hoarder
|
||||
|
||||
<span class="badge badge--secondary">Support level: Community</span>
|
||||
|
||||
## What is Hoarder
|
||||
|
||||
> A self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full-text search.
|
||||
>
|
||||
> -- https://hoarder.app/
|
||||
|
||||
## Preparation
|
||||
|
||||
The following placeholders will be used:
|
||||
|
||||
- `hoarder.company` is the FQDN of the Hoarder install.
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
|
||||
## authentik configuration
|
||||
|
||||
### Provider settings
|
||||
|
||||
In authentik, under **Applications** -> **Providers** of the **Admin interface**, create a new **OAuth2/OpenID Provider** with the desired settings.
|
||||
|
||||
- Name: `hoarder`
|
||||
- Redirect URI: `https://hoarder.company/api/auth/callback/custom`
|
||||
|
||||
Everything else is up to you, just make sure to grab the client ID and the client secret!
|
||||
|
||||
### Application settings
|
||||
|
||||
In authentik, under **Applications** -> **Applications** of the **Admin interface**, create a new Application with the **Create** button that uses `hoarder` provider.
|
||||
Optionally apply access restrictions to the application.
|
||||
|
||||
## Hoarder configuration
|
||||
|
||||
In Hoarder, you'll need to add these environment variables:
|
||||
|
||||
```sh
|
||||
NEXTAUTH_URL=https://hoarder.company
|
||||
OAUTH_CLIENT_ID=<Client ID from authentik>
|
||||
OAUTH_CLIENT_SECRET=<Client secret from authentik>
|
||||
OAUTH_WELLKNOWN_URL=https://authentik.company/application/o/hoarder/.well-known/openid-configuration
|
||||
OAUTH_PROVIDER_NAME=authentik
|
||||
OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING=true
|
||||
# Optional: You can add this if you only want to allow login with Authentik
|
||||
# DISABLE_PASSWORD_AUTH=true
|
||||
# Optional but highly recommended:
|
||||
# DISABLE_SIGNUPS=true
|
||||
```
|
||||
|
||||
Finally, restart the Hoarder server and test your configuration.
|
@ -16,6 +16,9 @@ sidebar_label: Home Assistant
|
||||
:::caution
|
||||
You might run into CSRF errors, this is caused by a technology Home-assistant uses and not authentik, see [this GitHub issue](https://github.com/goauthentik/authentik/issues/884#issuecomment-851542477).
|
||||
:::
|
||||
:::caution
|
||||
Only prefixes starting with `/auth` need to be proxied (excluding prefixes starting with `/auth/token`), see [this GitHub issue](https://github.com/BeryJu/hass-auth-header/issues/212). This can be configured in the reverse proxy (e.g. nginx, Traefik) or in authentik Provider's **Unauthorized Paths**.
|
||||
:::
|
||||
:::note
|
||||
For Home Assistant to work with authentik, a custom integration needs to be installed for Home Assistant.
|
||||
:::
|
||||
|
@ -9,12 +9,12 @@ sidebar_label: Proxmox VE
|
||||
|
||||
## What is Proxmox VE
|
||||
|
||||
> Proxmox Virtual Environment is an open source server virtualization management solution based on QEMU/KVM and LXC. You can manage virtual machines, containers, highly available clusters, storage and networks with an integrated, easy-to-use web interface or via CLI. Proxmox VE code is licensed under the GNU Affero General Public License, version 3. The project is developed and maintained by Proxmox Server Solutions GmbH.
|
||||
> Proxmox Virtual Environment is an open source server virtualization management solution based on QEMU/KVM and LXC. You can manage virtual machines, containers, highly available clusters, storage, and networks with an integrated, easy-to-use web interface or via CLI. Proxmox VE code is licensed under the GNU Affero General Public License, version 3. The project is developed and maintained by Proxmox Server Solutions GmbH.
|
||||
>
|
||||
> -- https://pve.proxmox.com/wiki/Main_Page
|
||||
|
||||
:::caution
|
||||
This requires Proxmox VE 7.0 or newer.
|
||||
Requires Proxmox VE 7.0 or newer.
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
@ -24,36 +24,61 @@ The following placeholders will be used:
|
||||
- `proxmox.company` is the FQDN of the Proxmox VE server.
|
||||
- `authentik.company` is the FQDN of the authentik install.
|
||||
|
||||
### Step 1
|
||||
## authentik configuration
|
||||
|
||||
Under _Providers_, create an OAuth2/OpenID provider with these settings:
|
||||
1. In the Admin interface, navigate to **Applications -> Providers** to create an OAuth2/OpenID provider with these settings:
|
||||
|
||||
- Name: proxmox
|
||||
- Redirect URI: `https://proxmox.company:8006` (Note the absence of the trailing slash, and the inclusion of the webinterface port)
|
||||
- Signing Key: Select any available key
|
||||
- **Name:** proxmox
|
||||
- **Redirect URI:** `https://proxmox.company:8006` (No trailing slash, include the web interface port)
|
||||
- **Signing Key:** Select any available key
|
||||
|
||||
### Step 2
|
||||
2. Create an application using the provider.
|
||||
- Under **Applications** > **Applications** in the Admin interface, create a new application and configure it to use the provider created in the previous step.
|
||||
- Optionally, apply access restrictions to the application.
|
||||
- Set the **Launch URL** to `https://proxmox.company:8006`.
|
||||
|
||||
Create an application which uses this provider. Optionally apply access restrictions to the application.
|
||||
## Proxmox VE configuration (using the web interface)
|
||||
|
||||
Set the Launch URL to `https://proxmox.company:8006`.
|
||||
1. Log in to the Proxmox VE web interface using an administrative account.
|
||||
|
||||
## Proxmox VE Setup
|
||||
2. Navigate to authentication source settings.
|
||||
|
||||
Proxmox VE allows configuration of authentication sources using the web interface (under Datacenter -> Permissions -> Realms).
|
||||
- Go to **Datacenter** > **Permissions** > **Realms**.
|
||||
- Click **Add** and select **Realm** to open the Add Realm dialog.
|
||||
|
||||

|
||||
3. Fill out the OpenID Connect settings.
|
||||
|
||||
Another way is to use the CLI. SSH into any Proxmox cluster node, and issue the following command:
|
||||
- In the dialog that appears, fill in the following details:
|
||||
- **Issuer URL**: Enter the Issuer URL from authentik (found in your provider's overview tab), e.g., `https://authentik.company/application/o/proxmox/`.
|
||||
- **Realm**: Enter a name for this authentication source, such as `authentik`.
|
||||
- **Client ID**: Enter the Client ID found on the provider overview page.
|
||||
- **Client Key**: Enter the Client Secret. (To find this value click **Edit** on the Provider overview page.)
|
||||
- **Username claim**: Set this to `username`.
|
||||
- **Autocreate users**: Check this box if you want Proxmox to automatically create users upon first login. If checked, users will appear in Proxmox with the format `<authentik username>@authentik`.
|
||||
- **Default**: Check this if you want OpenID Connect to be pre-selected as the default on the login screen.
|
||||
|
||||
`pveum realm add authentik --type openid --issuer-url https://authentik.company/application/o/proxmox/ --client-id xxx --client-key xxx --username-claim username --autocreate 1`
|
||||
**Example configuration**:
|
||||
|
||||
You can find the Issuer URL on the Provider Metadata tab in authentik. You can find the Client ID and Key on the Provider Edit dialog in authentik.
|
||||

|
||||
|
||||
After configuring the source in Proxmox, any user that logs in to Proxmox for the first time automatically gets an user named `<authentik username>@<pve realm name>`. In this example,
|
||||
authentik user `bob` will get an user named `bob@authentik` in Proxmox. You can then assign Permissions as normally in Proxmox. You can also pre-create the users in Proxmox if you want
|
||||
the user to be able to perform actions immediately after first login.
|
||||
4. **Save the configuration**.
|
||||
|
||||
There is no way to directly trigger an OpenID Connect login in Proxmox, but if you set the source as 'default', it will be automatically selected on the Proxmox login screen.
|
||||
- Click **Add** to save the settings.
|
||||
|
||||

|
||||
5. **Assign permissions**
|
||||
|
||||
- After setting up the authentication source, go to **Permissions** to assign roles and permissions for each user as needed.
|
||||
|
||||
6. **Logging in**
|
||||
|
||||
- Users can select this authentication method from the Proxmox login screen, or if set as default, it will be automatically selected.
|
||||
|
||||

|
||||
|
||||
## Proxmox VE configuration (using CLI)
|
||||
|
||||
To configure OpenID Connect authentication via the CLI, SSH into any Proxmox cluster node and use the following command:
|
||||
|
||||
```bash
|
||||
pveum realm add authentik --type openid --issuer-url https://authentik.company/application/o/proxmox/ --client-id xxx --client-key xxx --username-claim username --autocreate 1
|
||||
```
|
||||
|
@ -463,13 +463,25 @@
|
||||
|
||||
[[redirects]]
|
||||
from = "/docs/policies/working_with_policies/unique_email"
|
||||
to = "/docs/customize/policies/working_with_policies/unique_email"
|
||||
to = "/docs/customize/policies/expression/unique_email"
|
||||
status = 302
|
||||
force = true
|
||||
|
||||
[[redirects]]
|
||||
from = "/docs/customize/policies/working_with_policies/unique_email"
|
||||
to = "/docs/customize/policies/expression/unique_email"
|
||||
status = 302
|
||||
force = true
|
||||
|
||||
[[redirects]]
|
||||
from = "/docs/policies/working_with_policies/whitelist_email"
|
||||
to = "/docs/customize/policies/working_with_policies/whitelist_email"
|
||||
to = "/docs/customize/policies/expression/whitelist_email"
|
||||
status = 302
|
||||
force = true
|
||||
|
||||
[[redirects]]
|
||||
from = "/docs/customize/policies/working_with_policies/whitelist_email"
|
||||
to = "/docs/customize/policies/expression/whitelist_email"
|
||||
status = 302
|
||||
force = true
|
||||
|
||||
|
16
website/package-lock.json
generated
16
website/package-lock.json
generated
@ -21,7 +21,7 @@
|
||||
"docusaurus-plugin-openapi-docs": "^4.3.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.3.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prism-react-renderer": "^2.4.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -35,7 +35,7 @@
|
||||
"@docusaurus/tsconfig": "^3.6.3",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@types/react": "^18.3.13",
|
||||
"aws-cdk": "^2.172.0",
|
||||
"aws-cdk": "^2.173.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "~5.7.2",
|
||||
@ -6121,9 +6121,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.172.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.172.0.tgz",
|
||||
"integrity": "sha512-kacztcAl12F6zlBqKCuzCZmj4vrbMhzgDAxBB4T7fXR2amQyuu6W0nWcGWWvASXeBJcw2DJ6ulpfV4wuc9dksw==",
|
||||
"version": "2.173.1",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.173.1.tgz",
|
||||
"integrity": "sha512-1KWz6ZPPpBk3LyxE+iR4Gi1bbdY5N6Zj7kx/26jqvavBfZle93vT3M0jlTKI6v/bBtpYsVHTOmPFcq0fg1DfCw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cdk": "bin/cdk"
|
||||
@ -19000,9 +19000,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prism-react-renderer": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz",
|
||||
"integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz",
|
||||
"integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==",
|
||||
"dependencies": {
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"clsx": "^2.0.0"
|
||||
|
@ -30,7 +30,7 @@
|
||||
"docusaurus-plugin-openapi-docs": "^4.3.0",
|
||||
"docusaurus-theme-openapi-docs": "^4.3.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prism-react-renderer": "^2.4.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^18.3.1",
|
||||
"react-before-after-slider-component": "^1.1.8",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -56,7 +56,7 @@
|
||||
"@docusaurus/tsconfig": "^3.6.3",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@types/react": "^18.3.13",
|
||||
"aws-cdk": "^2.172.0",
|
||||
"aws-cdk": "^2.173.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "3.4.2",
|
||||
"typescript": "~5.7.2",
|
||||
|
@ -297,6 +297,7 @@ export default {
|
||||
"add-secure-apps/flows-stages/stages/invitation/index",
|
||||
"add-secure-apps/flows-stages/stages/password/index",
|
||||
"add-secure-apps/flows-stages/stages/prompt/index",
|
||||
"add-secure-apps/flows-stages/stages/redirect/index",
|
||||
"add-secure-apps/flows-stages/stages/source/index",
|
||||
"add-secure-apps/flows-stages/stages/user_delete",
|
||||
"add-secure-apps/flows-stages/stages/user_login/index",
|
||||
@ -352,19 +353,20 @@ export default {
|
||||
id: "customize/policies/index",
|
||||
},
|
||||
items: [
|
||||
"customize/policies/working_with_policies",
|
||||
{
|
||||
type: "category",
|
||||
label: "Working with Policies",
|
||||
label: "Expression Policies",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "customize/policies/working_with_policies/working_with_policies",
|
||||
id: "customize/policies/expression",
|
||||
},
|
||||
items: [
|
||||
"customize/policies/working_with_policies/unique_email",
|
||||
"customize/policies/working_with_policies/whitelist_email",
|
||||
"customize/policies/expression/unique_email",
|
||||
"customize/policies/expression/whitelist_email",
|
||||
"customize/policies/expression/managing_flow_context_keys",
|
||||
],
|
||||
},
|
||||
"customize/policies/expression",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -122,6 +122,7 @@ module.exports = {
|
||||
"services/frappe/index",
|
||||
"services/freshrss/index",
|
||||
"services/gravitee/index",
|
||||
"services/hoarder/index",
|
||||
"services/home-assistant/index",
|
||||
"services/immich/index",
|
||||
"services/jellyfin/index",
|
||||
|
Reference in New Issue
Block a user