blueprints: v1 (#1573)

* managed: move flowexporter to managed

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* *: implement SerializerModel in all models

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* managed: add initial api

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* managed: start blueprint

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* managed: spec

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* version blueprint

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* yep

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* remove v2, improve v1

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* start custom tag, more rebrand

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add default flows

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* move blueprints out of website

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* try new things

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add !lookup, fix web

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* update and cleanup default

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix tags in lists

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* don't save field if its set to default value

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* more flow cleanup

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* format web

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix missing serializer for sms

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* ignore _set fields

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* remove custom file extension

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* migrate default flow to tenant

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* include blueprints

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2022-07-31 17:11:44 +02:00 committed by GitHub
parent 882250a85e
commit 89c84f10d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
119 changed files with 2171 additions and 748 deletions

View file

@ -20,9 +20,10 @@
"todo-tree.tree.showCountsInTree": true,
"todo-tree.tree.showBadges": true,
"python.formatting.provider": "black",
"files.associations": {
"*.akflow": "yaml"
},
"yaml.customTags": [
"!Find sequence",
"!KeyOf scalar"
],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "index",
"typescript.tsdk": "./web/node_modules/typescript/lib",

View file

@ -81,6 +81,7 @@ COPY ./pyproject.toml /
COPY ./xml /xml
COPY ./tests /tests
COPY ./manage.py /
COPY ./blueprints/default /blueprints
COPY ./lifecycle/ /lifecycle
COPY --from=builder /work/authentik /authentik-proxy
COPY --from=web-builder /work/web/dist/ /web/dist/

View file

@ -5,10 +5,10 @@ from django.test import TestCase
from django.urls import reverse
from authentik import __version__
from authentik.blueprints.tasks import managed_reconcile
from authentik.core.models import Group, User
from authentik.core.tasks import clean_expired_models
from authentik.events.monitored_tasks import TaskResultStatus
from authentik.managed.tasks import managed_reconcile
class TestAdminAPI(TestCase):

View file

@ -12,6 +12,7 @@ from authentik.admin.api.version import VersionView
from authentik.admin.api.workers import WorkerView
from authentik.api.v3.config import ConfigView
from authentik.api.views import APIBrowserView
from authentik.blueprints.api import BlueprintInstanceViewSet
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
@ -131,6 +132,8 @@ router.register("events/notifications", NotificationViewSet)
router.register("events/transports", NotificationTransportViewSet)
router.register("events/rules", NotificationRuleViewSet)
router.register("managed/blueprints", BlueprintInstanceViewSet)
router.register("sources/all", SourceViewSet)
router.register("sources/user_connections/all", UserSourceConnectionViewSet)
router.register("sources/user_connections/oauth", UserOAuthSourceConnectionViewSet)

View file

@ -0,0 +1,56 @@
"""Serializer mixin for managed models"""
from glob import glob
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.blueprints.models import BlueprintInstance
from authentik.lib.config import CONFIG
class ManagedSerializer:
"""Managed Serializer"""
managed = CharField(read_only=True, allow_null=True)
class BlueprintInstanceSerializer(ModelSerializer):
"""Info about a single blueprint instance file"""
class Meta:
model = BlueprintInstance
fields = [
"name",
"path",
"context",
"last_applied",
"status",
"enabled",
]
class BlueprintInstanceViewSet(ModelViewSet):
"""Blueprint instances"""
permission_classes = [IsAdminUser]
serializer_class = BlueprintInstanceSerializer
queryset = BlueprintInstance.objects.all()
search_fields = ["name", "path"]
filterset_fields = ["name", "path"]
@extend_schema(responses={200: ListSerializer(child=CharField())})
@action(detail=False, pagination_class=None, filter_backends=[])
def available(self, request: Request) -> Response:
"""Get blueprints"""
files = []
for folder in CONFIG.y("blueprint_locations"):
for file in glob(f"{folder}/**", recursive=True):
files.append(file)
return Response(files)

View file

@ -0,0 +1,15 @@
"""authentik Blueprints app"""
from django.apps import AppConfig
class AuthentikBlueprintsConfig(AppConfig):
"""authentik Blueprints app"""
name = "authentik.blueprints"
label = "authentik_blueprints"
verbose_name = "authentik Blueprints"
def ready(self) -> None:
from authentik.blueprints.tasks import managed_reconcile
managed_reconcile.delay()

View file

@ -0,0 +1,22 @@
"""Apply blueprint from commandline"""
from django.core.management.base import BaseCommand, no_translations
from authentik.blueprints.v1.importer import Importer
class Command(BaseCommand): # pragma: no cover
"""Apply blueprint from commandline"""
@no_translations
def handle(self, *args, **options):
"""Apply all blueprints in order, abort when one fails to import"""
for blueprint_path in options.get("blueprints", []):
with open(blueprint_path, "r", encoding="utf8") as blueprint_file:
importer = Importer(blueprint_file.read())
valid = importer.validate()
if not valid:
raise ValueError("blueprint invalid")
importer.apply()
def add_arguments(self, parser):
parser.add_argument("blueprints", nargs="+", type=str)

View file

@ -3,7 +3,7 @@ from typing import Callable, Optional
from structlog.stdlib import get_logger
from authentik.managed.models import ManagedModel
from authentik.blueprints.models import ManagedModel
LOGGER = get_logger()

View file

@ -0,0 +1,66 @@
# Generated by Django 4.0.6 on 2022-07-30 22:45
import uuid
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="BlueprintInstance",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"managed",
models.TextField(
default=None,
help_text="Objects which are managed by authentik. These objects are created and updated automatically. This is flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
(
"instance_uuid",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("name", models.TextField()),
("path", models.TextField()),
("context", models.JSONField()),
("last_applied", models.DateTimeField(auto_now=True)),
(
"status",
models.TextField(
choices=[
("successful", "Successful"),
("warning", "Warning"),
("error", "Error"),
("unknown", "Unknown"),
]
),
),
("enabled", models.BooleanField(default=True)),
(
"managed_models",
django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(), size=None
),
),
],
options={
"verbose_name": "Blueprint Instance",
"verbose_name_plural": "Blueprint Instances",
"unique_together": {("name", "path")},
},
),
]

View file

@ -0,0 +1,76 @@
"""Managed Object models"""
from uuid import uuid4
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
class ManagedModel(models.Model):
"""Model which can be managed by authentik exclusively"""
managed = models.TextField(
default=None,
null=True,
verbose_name=_("Managed by authentik"),
help_text=_(
(
"Objects which are managed by authentik. These objects are created and updated "
"automatically. This is flag only indicates that an object can be overwritten by "
"migrations. You can still modify the objects via the API, but expect changes "
"to be overwritten in a later update."
)
),
unique=True,
)
class Meta:
abstract = True
class BlueprintInstanceStatus(models.TextChoices):
"""Instance status"""
SUCCESSFUL = "successful"
WARNING = "warning"
ERROR = "error"
UNKNOWN = "unknown"
class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel):
"""Instance of a single blueprint. Can be parameterized via context attribute when
blueprint in `path` has inputs."""
instance_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField()
path = models.TextField()
context = models.JSONField()
last_applied = models.DateTimeField(auto_now=True)
status = models.TextField(choices=BlueprintInstanceStatus.choices)
enabled = models.BooleanField(default=True)
managed_models = ArrayField(models.TextField())
@property
def serializer(self) -> Serializer:
from authentik.blueprints.api import BlueprintInstanceSerializer
return BlueprintInstanceSerializer
def __str__(self) -> str:
return f"Blueprint Instance {self.name}"
class Meta:
verbose_name = _("Blueprint Instance")
verbose_name_plural = _("Blueprint Instances")
unique_together = (
(
"name",
"path",
),
)

View file

@ -0,0 +1,17 @@
"""managed Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"blueprints_reconcile": {
"task": "authentik.blueprints.tasks.managed_reconcile",
"schedule": crontab(minute=fqdn_rand("managed_reconcile"), hour="*/4"),
"options": {"queue": "authentik_scheduled"},
},
"blueprints_config_file_discovery": {
"task": "authentik.blueprints.tasks.config_file_discovery",
"schedule": crontab(minute=fqdn_rand("config_file_discovery"), hour="*"),
"options": {"queue": "authentik_scheduled"},
},
}

View file

@ -1,6 +1,8 @@
"""managed tasks"""
from django.db import DatabaseError
from django.db.utils import ProgrammingError
from authentik.blueprints.manager import ObjectManager
from authentik.core.tasks import CELERY_APP
from authentik.events.monitored_tasks import (
MonitoredTask,
@ -8,7 +10,6 @@ from authentik.events.monitored_tasks import (
TaskResultStatus,
prefill_task,
)
from authentik.managed.manager import ObjectManager
@CELERY_APP.task(
@ -24,6 +25,5 @@ def managed_reconcile(self: MonitoredTask):
self.set_status(
TaskResult(TaskResultStatus.SUCCESSFUL, ["Successfully updated managed models."])
)
except DatabaseError as exc: # pragma: no cover
except (DatabaseError, ProgrammingError) as exc: # pragma: no cover
self.set_status(TaskResult(TaskResultStatus.WARNING, [str(exc)]))
self.retry()

View file

View file

@ -1,7 +1,7 @@
"""managed tests"""
from django.test import TestCase
from authentik.managed.tasks import managed_reconcile
from authentik.blueprints.tasks import managed_reconcile
class TestManaged(TestCase):

View file

@ -0,0 +1,34 @@
"""authentik managed models tests"""
from typing import Callable, Type
from django.apps import apps
from django.test import TestCase
from authentik.blueprints.v1.importer import EXCLUDED_MODELS
from authentik.lib.models import SerializerModel
class TestModels(TestCase):
"""Test Models"""
def serializer_tester_factory(test_model: Type[SerializerModel]) -> Callable:
"""Test serializer"""
def tester(self: TestModels):
if test_model._meta.abstract:
return
model_class = test_model()
self.assertTrue(isinstance(model_class, SerializerModel))
self.assertIsNotNone(model_class.serializer)
return tester
for app in apps.get_app_configs():
if not app.label.startswith("authentik"):
continue
for model in app.get_models():
if model in EXCLUDED_MODELS:
continue
setattr(TestModels, f"test_{app.label}_{model.__name__}", serializer_tester_factory(model))

View file

@ -1,11 +1,9 @@
"""Test flow transfer"""
"""Test flow Transport"""
from django.test import TransactionTestCase
from yaml import dump
from authentik.blueprints.v1.exporter import Exporter
from authentik.blueprints.v1.importer import Importer, transaction_rollback
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.transfer.common import DataclassDumper
from authentik.flows.transfer.exporter import FlowExporter
from authentik.flows.transfer.importer import FlowImporter, transaction_rollback
from authentik.lib.generators import generate_id
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
@ -33,14 +31,14 @@ STATIC_PROMPT_EXPORT = """{
}"""
class TestFlowTransfer(TransactionTestCase):
"""Test flow transfer"""
class TestFlowTransport(TransactionTestCase):
"""Test flow Transport"""
def test_bundle_invalid_format(self):
"""Test bundle with invalid format"""
importer = FlowImporter('{"version": 3}')
importer = Importer('{"version": 3}')
self.assertFalse(importer.validate())
importer = FlowImporter(
importer = Importer(
(
'{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
'"model": "authentik_core.User"}]}'
@ -66,12 +64,12 @@ class TestFlowTransfer(TransactionTestCase):
order=0,
)
exporter = FlowExporter(flow)
exporter = Exporter(flow)
export = exporter.export()
self.assertEqual(len(export.entries), 3)
export_yaml = exporter.export_to_string()
importer = FlowImporter(export_yaml)
importer = Importer(export_yaml)
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())
@ -81,14 +79,14 @@ class TestFlowTransfer(TransactionTestCase):
"""Test export and import it twice"""
count_initial = Prompt.objects.filter(field_key="username").count()
importer = FlowImporter(STATIC_PROMPT_EXPORT)
importer = Importer(STATIC_PROMPT_EXPORT)
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())
count_before = Prompt.objects.filter(field_key="username").count()
self.assertEqual(count_initial + 1, count_before)
importer = FlowImporter(STATIC_PROMPT_EXPORT)
importer = Importer(STATIC_PROMPT_EXPORT)
self.assertTrue(importer.apply())
self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before)
@ -114,12 +112,10 @@ class TestFlowTransfer(TransactionTestCase):
fsb = FlowStageBinding.objects.create(target=flow, stage=user_login, order=0)
PolicyBinding.objects.create(policy=flow_policy, target=fsb, order=0)
exporter = FlowExporter(flow)
export = exporter.export()
exporter = Exporter(flow)
export_yaml = exporter.export_to_string()
export_yaml = dump(export, Dumper=DataclassDumper)
importer = FlowImporter(export_yaml)
importer = Importer(export_yaml)
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())
self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
@ -159,11 +155,10 @@ class TestFlowTransfer(TransactionTestCase):
FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
exporter = FlowExporter(flow)
export = exporter.export()
export_yaml = dump(export, Dumper=DataclassDumper)
exporter = Exporter(flow)
export_yaml = exporter.export_to_string()
importer = FlowImporter(export_yaml)
importer = Importer(export_yaml)
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())

View file

@ -5,25 +5,25 @@ from typing import Callable
from django.test import TransactionTestCase
from authentik.flows.transfer.importer import FlowImporter
from authentik.blueprints.v1.importer import Importer
class TestTransferDocs(TransactionTestCase):
class TestTransportDocs(TransactionTestCase):
"""Empty class, test methods are added dynamically"""
def pbflow_tester(file_name: str) -> Callable:
"""This is used instead of subTest for better visibility"""
def tester(self: TestTransferDocs):
def tester(self: TestTransportDocs):
with open(file_name, "r", encoding="utf8") as flow_json:
importer = FlowImporter(flow_json.read())
importer = Importer(flow_json.read())
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())
return tester
for flow_file in glob("website/static/flows/*.akflow"):
for flow_file in glob("website/static/flows/*.yaml"):
method_name = Path(flow_file).stem.replace("-", "_").replace(".", "_")
setattr(TestTransferDocs, f"test_flow_{method_name}", pbflow_tester(flow_file))
setattr(TestTransportDocs, f"test_flow_{method_name}", pbflow_tester(flow_file))

View file

View file

@ -0,0 +1,177 @@
"""transfer common classes"""
from collections import OrderedDict
from dataclasses import asdict, dataclass, field, is_dataclass
from enum import Enum
from typing import Any, Optional
from uuid import UUID
from django.apps import apps
from django.db.models import Model, Q
from rest_framework.fields import Field
from rest_framework.serializers import Serializer
from yaml import SafeDumper, SafeLoader, ScalarNode, SequenceNode
from authentik.lib.models import SerializerModel
from authentik.lib.sentry import SentryIgnoredException
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"""Get object's attributes via their serializer, and convert it to a normal dict"""
serializer: Serializer = obj.serializer(obj)
data = dict(serializer.data)
for field_name, _field in serializer.fields.items():
_field: Field
if field_name not in data:
continue
if _field.read_only:
data.pop(field_name, None)
if _field.default == data.get(field_name, None):
data.pop(field_name, None)
if field_name.endswith("_set"):
data.pop(field_name, None)
return data
@dataclass
class BlueprintEntry:
"""Single entry of a bundle"""
identifiers: dict[str, Any]
model: str
attrs: dict[str, Any]
# pylint: disable=invalid-name
id: Optional[str] = None
_instance: Optional[Model] = None
@staticmethod
def from_model(model: SerializerModel, *extra_identifier_names: str) -> "BlueprintEntry":
"""Convert a SerializerModel instance to a Bundle Entry"""
identifiers = {
"pk": model.pk,
}
all_attrs = get_attrs(model)
for extra_identifier_name in extra_identifier_names:
identifiers[extra_identifier_name] = all_attrs.pop(extra_identifier_name)
return BlueprintEntry(
identifiers=identifiers,
model=f"{model._meta.app_label}.{model._meta.model_name}",
attrs=all_attrs,
)
def tag_resolver(self, value: Any, blueprint: "Blueprint") -> Any:
"""Check if we have any special tags that need handling"""
if isinstance(value, YAMLTag):
return value.resolve(self, blueprint)
if isinstance(value, dict):
for key, inner_value in value.items():
value[key] = self.tag_resolver(inner_value, blueprint)
if isinstance(value, list):
for idx, inner_value in enumerate(value):
value[idx] = self.tag_resolver(inner_value, blueprint)
return value
def get_attrs(self, blueprint: "Blueprint") -> dict[str, Any]:
"""Get attributes of this entry, with all yaml tags resolved"""
return self.tag_resolver(self.attrs, blueprint)
def get_identifiers(self, blueprint: "Blueprint") -> dict[str, Any]:
"""Get attributes of this entry, with all yaml tags resolved"""
return self.tag_resolver(self.identifiers, blueprint)
@dataclass
class Blueprint:
"""Dataclass used for a full export"""
version: int = field(default=1)
entries: list[BlueprintEntry] = field(default_factory=list)
class YAMLTag:
"""Base class for all YAML Tags"""
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
"""Implement yaml tag logic"""
raise NotImplementedError
class KeyOf(YAMLTag):
"""Reference another object by their ID"""
id_from: str
# pylint: disable=unused-argument
def __init__(self, loader: "BlueprintLoader", node: ScalarNode) -> None:
super().__init__()
self.id_from = node.value
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
for _entry in blueprint.entries:
if _entry.id == self.id_from and _entry._instance:
return _entry._instance.pk
raise ValueError(
f"KeyOf: failed to find entry with `id` of `{self.id_from}` and a model instance"
)
class Find(YAMLTag):
"""Find any object"""
model_name: str
conditions: list[list]
model_class: type[Model]
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
super().__init__()
self.model_name = node.value[0].value
self.model_class = apps.get_model(*self.model_name.split("."))
self.conditions = []
for raw_node in node.value[1:]:
values = []
for node_values in raw_node.value:
values.append(loader.construct_object(node_values))
self.conditions.append(values)
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
query = Q()
for cond in self.conditions:
query &= Q(**{cond[0]: cond[1]})
instance = self.model_class.objects.filter(query).first()
if instance:
return instance.pk
return None
class BlueprintDumper(SafeDumper):
"""Dump dataclasses to yaml"""
default_flow_style = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_representer(UUID, lambda self, data: self.represent_str(str(data)))
self.add_representer(OrderedDict, lambda self, data: self.represent_dict(dict(data)))
self.add_representer(Enum, lambda self, data: self.represent_str(data.value))
def represent(self, data) -> None:
if is_dataclass(data):
data = asdict(data)
return super().represent(data)
class BlueprintLoader(SafeLoader):
"""Loader for blueprints with custom tag support"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_constructor("!KeyOf", KeyOf)
self.add_constructor("!Find", Find)
class EntryInvalidError(SentryIgnoredException):
"""Error raised when an entry is invalid"""

View file

@ -5,14 +5,14 @@ from uuid import UUID
from django.db.models import Q
from yaml import dump
from authentik.blueprints.v1.common import Blueprint, BlueprintDumper, BlueprintEntry
from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.flows.transfer.common import DataclassDumper, FlowBundle, FlowBundleEntry
from authentik.policies.models import Policy, PolicyBinding
from authentik.stages.prompt.models import PromptStage
class FlowExporter:
"""Export flow with attached stages into json"""
class Exporter:
"""Export flow with attached stages into yaml"""
flow: Flow
with_policies: bool
@ -31,21 +31,21 @@ class FlowExporter:
"pbm_uuid", flat=True
)
def walk_stages(self) -> Iterator[FlowBundleEntry]:
"""Convert all stages attached to self.flow into FlowBundleEntry objects"""
def walk_stages(self) -> Iterator[BlueprintEntry]:
"""Convert all stages attached to self.flow into BlueprintEntry objects"""
stages = Stage.objects.filter(flow=self.flow).select_related().select_subclasses()
for stage in stages:
if isinstance(stage, PromptStage):
pass
yield FlowBundleEntry.from_model(stage, "name")
yield BlueprintEntry.from_model(stage, "name")
def walk_stage_bindings(self) -> Iterator[FlowBundleEntry]:
"""Convert all bindings attached to self.flow into FlowBundleEntry objects"""
def walk_stage_bindings(self) -> Iterator[BlueprintEntry]:
"""Convert all bindings attached to self.flow into BlueprintEntry objects"""
bindings = FlowStageBinding.objects.filter(target=self.flow).select_related()
for binding in bindings:
yield FlowBundleEntry.from_model(binding, "target", "stage", "order")
yield BlueprintEntry.from_model(binding, "target", "stage", "order")
def walk_policies(self) -> Iterator[FlowBundleEntry]:
def walk_policies(self) -> Iterator[BlueprintEntry]:
"""Walk over all policies. This is done at the beginning of the export for stages that have
a direct foreign key to a policy."""
# Special case for PromptStage as that has a direct M2M to policy, we have to ensure
@ -54,28 +54,28 @@ class FlowExporter:
query = Q(bindings__in=self.pbm_uuids) | Q(promptstage__in=prompt_stages)
policies = Policy.objects.filter(query).select_related()
for policy in policies:
yield FlowBundleEntry.from_model(policy)
yield BlueprintEntry.from_model(policy)
def walk_policy_bindings(self) -> Iterator[FlowBundleEntry]:
def walk_policy_bindings(self) -> Iterator[BlueprintEntry]:
"""Walk over all policybindings relative to us. This is run at the end of the export, as
we are sure all objects exist now."""
bindings = PolicyBinding.objects.filter(target__in=self.pbm_uuids).select_related()
for binding in bindings:
yield FlowBundleEntry.from_model(binding, "policy", "target", "order")
yield BlueprintEntry.from_model(binding, "policy", "target", "order")
def walk_stage_prompts(self) -> Iterator[FlowBundleEntry]:
def walk_stage_prompts(self) -> Iterator[BlueprintEntry]:
"""Walk over all prompts associated with any PromptStages"""
prompt_stages = PromptStage.objects.filter(flow=self.flow)
for stage in prompt_stages:
for prompt in stage.fields.all():
yield FlowBundleEntry.from_model(prompt)
yield BlueprintEntry.from_model(prompt)
def export(self) -> FlowBundle:
def export(self) -> Blueprint:
"""Create a list of all objects including the flow"""
if self.with_policies:
self._prepare_pbm()
bundle = FlowBundle()
bundle.entries.append(FlowBundleEntry.from_model(self.flow, "slug"))
bundle = Blueprint()
bundle.entries.append(BlueprintEntry.from_model(self.flow, "slug"))
if self.with_stage_prompts:
bundle.entries.extend(self.walk_stage_prompts())
if self.with_policies:
@ -87,6 +87,6 @@ class FlowExporter:
return bundle
def export_to_string(self) -> str:
"""Call export and convert it to json"""
"""Call export and convert it to yaml"""
bundle = self.export()
return dump(bundle, Dumper=DataclassDumper)
return dump(bundle, Dumper=BlueprintDumper)

View file

@ -1,4 +1,4 @@
"""Flow importer"""
"""Blueprint importer"""
from contextlib import contextmanager
from copy import deepcopy
from typing import Any
@ -13,15 +13,39 @@ from django.db.utils import IntegrityError
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import BaseSerializer, Serializer
from structlog.stdlib import BoundLogger, get_logger
from yaml import safe_load
from yaml import load
from authentik.flows.models import Flow, FlowStageBinding, Stage
from authentik.flows.transfer.common import EntryInvalidError, FlowBundle, FlowBundleEntry
from authentik.blueprints.v1.common import (
Blueprint,
BlueprintEntry,
BlueprintLoader,
EntryInvalidError,
)
from authentik.core.models import (
AuthenticatedSession,
PropertyMapping,
Provider,
Source,
UserSourceConnection,
)
from authentik.flows.models import Stage
from authentik.lib.models import SerializerModel
from authentik.policies.models import Policy, PolicyBinding
from authentik.stages.prompt.models import Prompt
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
ALLOWED_MODELS = (Flow, FlowStageBinding, Stage, Policy, PolicyBinding, Prompt)
EXCLUDED_MODELS = (
# Base classes
Provider,
Source,
PropertyMapping,
UserSourceConnection,
Stage,
OutpostServiceConnection,
Policy,
PolicyBindingModel,
# Classes that have other dependencies
AuthenticatedSession,
)
@contextmanager
@ -34,17 +58,17 @@ def transaction_rollback():
atomic.__exit__(IntegrityError, None, None)
class FlowImporter:
"""Import Flow from json"""
class Importer:
"""Import Blueprint from YAML"""
logger: BoundLogger
def __init__(self, yaml_input: str):
self.__pk_map: dict[Any, Model] = {}
self.logger = get_logger()
import_dict = safe_load(yaml_input)
import_dict = load(yaml_input, BlueprintLoader)
try:
self.__import = from_dict(FlowBundle, import_dict)
self.__import = from_dict(Blueprint, import_dict)
except DaciteError as exc:
raise EntryInvalidError from exc
@ -75,7 +99,9 @@ class FlowImporter:
"""Generate an or'd query from all identifiers in an entry"""
# Since identifiers can also be pk-references to other objects (see FlowStageBinding)
# we have to ensure those references are also replaced
main_query = Q(pk=attrs["pk"])
main_query = Q()
if "pk" in attrs:
main_query = Q(pk=attrs["pk"])
sub_query = Q()
for identifier, value in attrs.items():
if isinstance(value, dict):
@ -85,11 +111,12 @@ class FlowImporter:
sub_query &= Q(**{identifier: value})
return main_query | sub_query
def _validate_single(self, entry: FlowBundleEntry) -> BaseSerializer:
def _validate_single(self, entry: BlueprintEntry) -> BaseSerializer:
"""Validate a single entry"""
model_app_label, model_name = entry.model.split(".")
model: type[SerializerModel] = apps.get_model(model_app_label, model_name)
if not isinstance(model(), ALLOWED_MODELS):
# Don't use isinstance since we don't want to check for inheritance
if model in EXCLUDED_MODELS:
raise EntryInvalidError(f"Model {model} not allowed")
# If we try to validate without referencing a possible instance
@ -97,7 +124,7 @@ class FlowImporter:
# the full serializer for later usage
# Because a model might have multiple unique columns, we chain all identifiers together
# to create an OR query.
updated_identifiers = self.__update_pks_for_attrs(entry.identifiers)
updated_identifiers = self.__update_pks_for_attrs(entry.get_identifiers(self.__import))
for key, value in list(updated_identifiers.items()):
if isinstance(value, dict) and "pk" in value:
del updated_identifiers[key]
@ -121,7 +148,7 @@ class FlowImporter:
if "pk" in updated_identifiers:
model_instance.pk = updated_identifiers["pk"]
serializer_kwargs["instance"] = model_instance
full_data = self.__update_pks_for_attrs(entry.attrs)
full_data = self.__update_pks_for_attrs(entry.get_attrs(self.__import))
full_data.update(updated_identifiers)
serializer_kwargs["data"] = full_data
@ -133,7 +160,7 @@ class FlowImporter:
return serializer
def apply(self) -> bool:
"""Apply (create/update) flow json, in database transaction"""
"""Apply (create/update) models yaml, in database transaction"""
try:
with transaction.atomic():
if not self._apply_models():
@ -146,10 +173,9 @@ class FlowImporter:
return True
def _apply_models(self) -> bool:
"""Apply (create/update) flow json"""
"""Apply (create/update) models yaml"""
self.__pk_map = {}
entries = deepcopy(self.__import.entries)
for entry in entries:
for entry in self.__import.entries:
model_app_label, model_name = entry.model.split(".")
try:
model: SerializerModel = apps.get_model(model_app_label, model_name)
@ -166,7 +192,9 @@ class FlowImporter:
return False
model = serializer.save()
self.__pk_map[entry.identifiers["pk"]] = model.pk
if "pk" in entry.identifiers:
self.__pk_map[entry.identifiers["pk"]] = model.pk
entry._instance = model
self.logger.debug("updated model", model=model, pk=model.pk)
return True
@ -174,6 +202,7 @@ class FlowImporter:
"""Validate loaded flow export, ensure all models are allowed
and serializers have no errors"""
self.logger.debug("Starting flow import validation")
orig_import = deepcopy(self.__import)
if self.__import.version != 1:
self.logger.warning("Invalid bundle version")
return False
@ -181,4 +210,5 @@ class FlowImporter:
successful = self._apply_models()
if not successful:
self.logger.debug("Flow validation failed")
self.__import = orig_import
return successful

View file

@ -14,12 +14,12 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import GenericViewSet
from authentik.api.decorators import permission_required
from authentik.blueprints.api import ManagedSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer, TypeCreateSerializer
from authentik.core.expression import PropertyMappingEvaluator
from authentik.core.models import PropertyMapping
from authentik.lib.utils.reflection import all_subclasses
from authentik.managed.api import ManagedSerializer
from authentik.policies.api.exec import PolicyTestSerializer

View file

@ -15,13 +15,13 @@ from rest_framework.viewsets import ModelViewSet
from authentik.api.authorization import OwnerSuperuserPermissions
from authentik.api.decorators import permission_required
from authentik.blueprints.api import ManagedSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents
from authentik.events.models import Event, EventAction
from authentik.events.utils import model_to_dict
from authentik.managed.api import ManagedSerializer
class TokenSerializer(ManagedSerializer, ModelSerializer):

View file

@ -1,6 +1,6 @@
"""Core managed objects"""
from authentik.blueprints.manager import EnsureExists, ObjectManager
from authentik.core.models import Source
from authentik.managed.manager import EnsureExists, ObjectManager
class CoreManager(ObjectManager):

View file

@ -20,9 +20,10 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from guardian.mixins import GuardianUserMixin
from model_utils.managers import InheritanceManager
from rest_framework.serializers import Serializer
from rest_framework.serializers import BaseSerializer, Serializer
from structlog.stdlib import get_logger
from authentik.blueprints.models import ManagedModel
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.core.signals import password_changed
from authentik.core.types import UILoginButton, UserSettingSerializer
@ -30,7 +31,6 @@ from authentik.lib.config import CONFIG, get_path_from_dict
from authentik.lib.generators import generate_id
from authentik.lib.models import CreatedUpdatedModel, DomainlessURLValidator, SerializerModel
from authentik.lib.utils.http import get_client_ip
from authentik.managed.models import ManagedModel
from authentik.policies.models import PolicyBindingModel
LOGGER = get_logger()
@ -68,7 +68,7 @@ def default_token_key():
return generate_id(int(CONFIG.y("default_token_length")))
class Group(models.Model):
class Group(SerializerModel):
"""Custom Group model which supports a basic hierarchy"""
group_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -87,6 +87,12 @@ class Group(models.Model):
)
attributes = models.JSONField(default=dict, blank=True)
@property
def serializer(self) -> Serializer:
from authentik.core.api.groups import GroupSerializer
return GroupSerializer
@property
def num_pk(self) -> int:
"""Get a numerical, int32 ID for the group"""
@ -139,7 +145,7 @@ class UserManager(DjangoUserManager):
return self._create_user(username, email, password, **extra_fields)
class User(GuardianUserMixin, AbstractUser):
class User(SerializerModel, GuardianUserMixin, AbstractUser):
"""Custom User model to allow easier adding of user-based settings"""
uuid = models.UUIDField(default=uuid4, editable=False)
@ -170,6 +176,12 @@ class User(GuardianUserMixin, AbstractUser):
always_merger.merge(final_attributes, self.attributes)
return final_attributes
@property
def serializer(self) -> Serializer:
from authentik.core.api.users import UserSerializer
return UserSerializer
@cached_property
def is_superuser(self) -> bool:
"""Get supseruser status based on membership in a group with superuser status"""
@ -276,7 +288,7 @@ class Provider(SerializerModel):
return self.name
class Application(PolicyBindingModel):
class Application(SerializerModel, PolicyBindingModel):
"""Every Application which uses authentik for authentication/identification/authorization
needs an Application record. Other authentication types can subclass this Model to
add custom fields and other properties"""
@ -307,6 +319,12 @@ class Application(PolicyBindingModel):
meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True)
@property
def serializer(self) -> Serializer:
from authentik.core.api.applications import ApplicationSerializer
return ApplicationSerializer
@property
def get_meta_icon(self) -> Optional[str]:
"""Get the URL to the App Icon image. If the name is /static or starts with http
@ -454,7 +472,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
return self.name
class UserSourceConnection(CreatedUpdatedModel):
class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
"""Connection between User and Source."""
user = models.ForeignKey(User, on_delete=models.CASCADE)
@ -462,6 +480,11 @@ class UserSourceConnection(CreatedUpdatedModel):
objects = InheritanceManager()
@property
def serializer(self) -> BaseSerializer:
"""Get serializer for this model"""
raise NotImplementedError
class Meta:
unique_together = (("user", "source"),)
@ -516,7 +539,7 @@ class TokenIntents(models.TextChoices):
INTENT_APP_PASSWORD = "app_password" # nosec
class Token(ManagedModel, ExpiringModel):
class Token(SerializerModel, ManagedModel, ExpiringModel):
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -528,6 +551,12 @@ class Token(ManagedModel, ExpiringModel):
user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="+")
description = models.TextField(default="", blank=True)
@property
def serializer(self) -> Serializer:
from authentik.core.api.tokens import TokenSerializer
return TokenSerializer
def expire_action(self, *args, **kwargs):
"""Handler which is called when this object is expired."""
from authentik.events.models import Event, EventAction

View file

@ -2,9 +2,9 @@
from datetime import datetime
from typing import Optional
from authentik.blueprints.manager import ObjectManager
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair
from authentik.managed.manager import ObjectManager
MANAGED_KEY = "goauthentik.io/crypto/jwt-managed"

View file

@ -16,15 +16,16 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import Certificate, load_pem_x509_certificate
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik.lib.models import CreatedUpdatedModel
from authentik.managed.models import ManagedModel
from authentik.blueprints.models import ManagedModel
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
LOGGER = get_logger()
class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
is set, otherwise it can be used to verify remote data."""
@ -44,6 +45,12 @@ class CertificateKeyPair(ManagedModel, CreatedUpdatedModel):
_private_key: Optional[RSAPrivateKey | EllipticCurvePrivateKey | Ed25519PrivateKey] = None
_public_key: Optional[RSAPublicKey | EllipticCurvePublicKey | Ed25519PublicKey] = None
@property
def serializer(self) -> Serializer:
from authentik.crypto.api import CertificateKeyPairSerializer
return CertificateKeyPairSerializer
@property
def certificate(self) -> Certificate:
"""Get python cryptography Certificate instance"""

View file

@ -30,7 +30,7 @@ from authentik.core.middleware import (
from authentik.core.models import ExpiringModel, Group, PropertyMapping, User
from authentik.events.geo import GEOIP_READER
from authentik.events.utils import cleanse_dict, get_user, model_to_dict, sanitize_dict
from authentik.lib.models import DomainlessURLValidator
from authentik.lib.models import DomainlessURLValidator, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.http import get_client_ip, get_http_session
from authentik.lib.utils.time import timedelta_from_string
@ -168,7 +168,7 @@ class EventManager(Manager):
return self.get_queryset().get_events_per_day()
class Event(ExpiringModel):
class Event(SerializerModel, ExpiringModel):
"""An individual Audit/Metrics/Notification/Error Event"""
event_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -273,6 +273,12 @@ class Event(ExpiringModel):
)
super().save(*args, **kwargs)
@property
def serializer(self) -> "Serializer":
from authentik.events.api.events import EventSerializer
return EventSerializer
@property
def summary(self) -> str:
"""Return a summary of this event."""
@ -298,7 +304,7 @@ class TransportMode(models.TextChoices):
EMAIL = "email", _("Email")
class NotificationTransport(models.Model):
class NotificationTransport(SerializerModel):
"""Action which is executed when a Rule matches"""
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -448,6 +454,12 @@ class NotificationTransport(models.Model):
except (SMTPException, ConnectionError, OSError) as exc:
raise NotificationTransportError from exc
@property
def serializer(self) -> "Serializer":
from authentik.events.api.notification_transports import NotificationTransportSerializer
return NotificationTransportSerializer
def __str__(self) -> str:
return f"Notification Transport {self.name}"
@ -465,7 +477,7 @@ class NotificationSeverity(models.TextChoices):
ALERT = "alert", _("Alert")
class Notification(models.Model):
class Notification(SerializerModel):
"""Event Notification"""
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -476,6 +488,12 @@ class Notification(models.Model):
seen = models.BooleanField(default=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
@property
def serializer(self) -> "Serializer":
from authentik.events.api.notifications import NotificationSerializer
return NotificationSerializer
def __str__(self) -> str:
body_trunc = (self.body[:75] + "..") if len(self.body) > 75 else self.body
return f"Notification for user {self.user}: {body_trunc}"
@ -486,7 +504,7 @@ class Notification(models.Model):
verbose_name_plural = _("Notifications")
class NotificationRule(PolicyBindingModel):
class NotificationRule(SerializerModel, PolicyBindingModel):
"""Decide when to create a Notification based on policies attached to this object."""
name = models.TextField(unique=True)
@ -518,6 +536,12 @@ class NotificationRule(PolicyBindingModel):
on_delete=models.SET_NULL,
)
@property
def serializer(self) -> "Serializer":
from authentik.events.api.notification_rules import NotificationRuleSerializer
return NotificationRuleSerializer
def __str__(self) -> str:
return f"Notification Rule {self.name}"

View file

@ -20,6 +20,8 @@ from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.decorators import permission_required
from authentik.blueprints.v1.exporter import Exporter
from authentik.blueprints.v1.importer import Importer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import (
CacheSerializer,
@ -30,8 +32,6 @@ from authentik.core.api.utils import (
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlanner, cache_key
from authentik.flows.transfer.exporter import FlowExporter
from authentik.flows.transfer.importer import FlowImporter
from authentik.flows.views.executor import SESSION_KEY_HISTORY, SESSION_KEY_PLAN
from authentik.lib.views import bad_request_message
@ -163,11 +163,11 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
)
@action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
def import_flow(self, request: Request) -> Response:
"""Import flow from .akflow file"""
"""Import flow from .yaml file"""
file = request.FILES.get("file", None)
if not file:
return HttpResponseBadRequest()
importer = FlowImporter(file.read().decode())
importer = Importer(file.read().decode())
valid = importer.validate()
if not valid:
return HttpResponseBadRequest()
@ -195,11 +195,11 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument
def export(self, request: Request, slug: str) -> Response:
"""Export flow to .akflow file"""
"""Export flow to .yaml file"""
flow = self.get_object()
exporter = FlowExporter(flow)
exporter = Exporter(flow)
response = HttpResponse(content=exporter.export_to_string())
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"'
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.yaml"'
return response
@extend_schema(responses={200: FlowDiagramSerializer()})

View file

@ -1,14 +1,16 @@
"""Challenge helpers"""
from dataclasses import asdict, is_dataclass
from enum import Enum
from typing import TYPE_CHECKING, Optional, TypedDict
from uuid import UUID
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.http import JsonResponse
from rest_framework.fields import ChoiceField, DictField
from rest_framework.serializers import CharField
from authentik.core.api.utils import PassiveSerializer
from authentik.flows.transfer.common import DataclassEncoder
if TYPE_CHECKING:
from authentik.flows.stage import StageView
@ -135,6 +137,19 @@ class AutoSubmitChallengeResponse(ChallengeResponse):
component = CharField(default="ak-stage-autosubmit")
class DataclassEncoder(DjangoJSONEncoder):
"""Convert any dataclass to json"""
def default(self, o):
if is_dataclass(o):
return asdict(o)
if isinstance(o, UUID):
return str(o)
if isinstance(o, Enum):
return o.value
return super().default(o) # pragma: no cover
class HttpChallengeResponse(JsonResponse):
"""Subclass of JsonResponse that uses the `DataclassEncoder`"""

View file

@ -1,22 +0,0 @@
"""Apply flow from commandline"""
from django.core.management.base import BaseCommand, no_translations
from authentik.flows.transfer.importer import FlowImporter
class Command(BaseCommand): # pragma: no cover
"""Apply flow from commandline"""
@no_translations
def handle(self, *args, **options):
"""Apply all flows in order, abort when one fails to import"""
for flow_path in options.get("flows", []):
with open(flow_path, "r", encoding="utf8") as flow_file:
importer = FlowImporter(flow_file.read())
valid = importer.validate()
if not valid:
raise ValueError("Flow invalid")
importer.apply()
def add_arguments(self, parser):
parser.add_argument("flows", nargs="+", type=str)

View file

@ -1,105 +0,0 @@
"""transfer common classes"""
from dataclasses import asdict, dataclass, field, is_dataclass
from enum import Enum
from typing import Any
from uuid import UUID
from django.core.serializers.json import DjangoJSONEncoder
from yaml import SafeDumper
from authentik.lib.models import SerializerModel
from authentik.lib.sentry import SentryIgnoredException
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"""Get object's attributes via their serializer, and convert it to a normal dict"""
data = dict(obj.serializer(obj).data)
to_remove = (
"policies",
"stages",
"pk",
"background",
"group",
"user",
"verbose_name",
"verbose_name_plural",
"component",
"flow_set",
"promptstage_set",
"policybindingmodel_ptr_id",
"export_url",
"meta_model_name",
)
for to_remove_name in to_remove:
if to_remove_name in data:
data.pop(to_remove_name)
for key in list(data.keys()):
if key.endswith("_obj"):
data.pop(key)
return data
@dataclass
class FlowBundleEntry:
"""Single entry of a bundle"""
identifiers: dict[str, Any]
model: str
attrs: dict[str, Any]
@staticmethod
def from_model(model: SerializerModel, *extra_identifier_names: str) -> "FlowBundleEntry":
"""Convert a SerializerModel instance to a Bundle Entry"""
identifiers = {
"pk": model.pk,
}
all_attrs = get_attrs(model)
for extra_identifier_name in extra_identifier_names:
identifiers[extra_identifier_name] = all_attrs.pop(extra_identifier_name)
return FlowBundleEntry(
identifiers=identifiers,
model=f"{model._meta.app_label}.{model._meta.model_name}",
attrs=all_attrs,
)
@dataclass
class FlowBundle:
"""Dataclass used for a full export"""
version: int = field(default=1)
entries: list[FlowBundleEntry] = field(default_factory=list)
class DataclassEncoder(DjangoJSONEncoder):
"""Convert FlowBundleEntry to json"""
def default(self, o):
if is_dataclass(o):
return asdict(o)
if isinstance(o, UUID):
return str(o)
if isinstance(o, Enum):
return o.value
return super().default(o) # pragma: no cover
class DataclassDumper(SafeDumper):
"""Dump dataclasses to yaml"""
default_flow_style = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_representer(UUID, lambda self, data: self.represent_str(str(data)))
self.add_representer(Enum, lambda self, data: self.represent_str(data.value))
def represent(self, data) -> None:
if is_dataclass(data):
data = asdict(data)
return super().represent(data)
class EntryInvalidError(SentryIgnoredException):
"""Error raised when an entry is invalid"""

View file

@ -62,6 +62,7 @@ ldap:
tls:
ciphers: null
config_file_dir: "/config"
cookie_domain: null
disable_update_check: false
disable_startup_analytics: false

View file

@ -1,8 +0,0 @@
"""authentik lib template utilities"""
from django.template import Context, loader
def render_to_string(template_path: str, ctx: Context) -> str:
"""Render a template to string"""
template = loader.get_template(template_path)
return template.render(ctx)

View file

@ -1,8 +0,0 @@
"""Serializer mixin for managed models"""
from rest_framework.fields import CharField
class ManagedSerializer:
"""Managed Serializer"""
managed = CharField(read_only=True, allow_null=True)

View file

@ -1,15 +0,0 @@
"""authentik Managed app"""
from django.apps import AppConfig
class AuthentikManagedConfig(AppConfig):
"""authentik Managed app"""
name = "authentik.managed"
label = "authentik_managed"
verbose_name = "authentik Managed"
def ready(self) -> None:
from authentik.managed.tasks import managed_reconcile
managed_reconcile.delay()

View file

@ -1,26 +0,0 @@
"""Managed Object models"""
from django.db import models
from django.utils.translation import gettext_lazy as _
class ManagedModel(models.Model):
"""Model which can be managed by authentik exclusively"""
managed = models.TextField(
default=None,
null=True,
verbose_name=_("Managed by authentik"),
help_text=_(
(
"Objects which are managed by authentik. These objects are created and updated "
"automatically. This is flag only indicates that an object can be overwritten by "
"migrations. You can still modify the objects via the API, but expect changes "
"to be overwritten in a later update."
)
),
unique=True,
)
class Meta:
abstract = True

View file

@ -1,12 +0,0 @@
"""managed Settings"""
from celery.schedules import crontab
from authentik.lib.utils.time import fqdn_rand
CELERY_BEAT_SCHEDULE = {
"managed_reconcile": {
"task": "authentik.managed.tasks.managed_reconcile",
"schedule": crontab(minute=fqdn_rand("managed_reconcile"), hour="*/4"),
"options": {"queue": "authentik_scheduled"},
},
}

View file

@ -1,5 +1,5 @@
"""Outpost managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.blueprints.manager import EnsureExists, ObjectManager
from authentik.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,

View file

@ -14,9 +14,11 @@ from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from model_utils.managers import InheritanceManager
from packaging.version import LegacyVersion, Version, parse
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash
from authentik.blueprints.models import ManagedModel
from authentik.core.models import (
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
USER_ATTRIBUTE_SA,
@ -29,10 +31,9 @@ from authentik.core.models import (
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.managed.models import ManagedModel
from authentik.outposts.controllers.k8s.utils import get_namespace
from authentik.tenants.models import Tenant
@ -155,7 +156,7 @@ class OutpostServiceConnection(models.Model):
verbose_name_plural = _("Outpost Service-Connections")
class DockerServiceConnection(OutpostServiceConnection):
class DockerServiceConnection(SerializerModel, OutpostServiceConnection):
"""Service Connection to a Docker endpoint"""
url = models.TextField(
@ -192,6 +193,12 @@ class DockerServiceConnection(OutpostServiceConnection):
),
)
@property
def serializer(self) -> Serializer:
from authentik.outposts.api.service_connections import DockerServiceConnectionSerializer
return DockerServiceConnectionSerializer
@property
def component(self) -> str:
return "ak-service-connection-docker-form"
@ -205,7 +212,7 @@ class DockerServiceConnection(OutpostServiceConnection):
verbose_name_plural = _("Docker Service-Connections")
class KubernetesServiceConnection(OutpostServiceConnection):
class KubernetesServiceConnection(SerializerModel, OutpostServiceConnection):
"""Service Connection to a Kubernetes cluster"""
kubeconfig = models.JSONField(
@ -218,6 +225,12 @@ class KubernetesServiceConnection(OutpostServiceConnection):
blank=True,
)
@property
def serializer(self) -> Serializer:
from authentik.outposts.api.service_connections import KubernetesServiceConnectionSerializer
return KubernetesServiceConnectionSerializer
@property
def component(self) -> str:
return "ak-service-connection-kubernetes-form"
@ -231,7 +244,7 @@ class KubernetesServiceConnection(OutpostServiceConnection):
verbose_name_plural = _("Kubernetes Service-Connections")
class Outpost(ManagedModel):
class Outpost(SerializerModel, ManagedModel):
"""Outpost instance which manages a service user and token"""
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
@ -256,6 +269,12 @@ class Outpost(ManagedModel):
providers = models.ManyToManyField(Provider)
@property
def serializer(self) -> Serializer:
from authentik.outposts.api.outposts import OutpostSerializer
return OutpostSerializer
@property
def config(self) -> OutpostConfig:
"""Load config as OutpostConfig object"""

View file

@ -2,7 +2,7 @@
from django.test import TestCase
from docker.models.containers import Container
from authentik.managed.manager import ObjectManager
from authentik.blueprints.manager import ObjectManager
from authentik.outposts.controllers.base import ControllerException
from authentik.outposts.controllers.docker import DockerController
from authentik.outposts.managed import MANAGED_OUTPOST

View file

@ -158,7 +158,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.tenants", "authentik Tenants"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",

View file

@ -69,7 +69,7 @@ class Migration(migrations.Migration):
"authentik Stages.OTP.Validate",
),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
("authentik.core", "authentik Core"),
],
default="",

View file

@ -73,7 +73,7 @@ class Migration(migrations.Migration):
"authentik.stages.authenticator_webauthn",
"authentik Stages.WebAuthn",
),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
("authentik.core", "authentik Core"),
],
default="",

View file

@ -76,7 +76,7 @@ class Migration(migrations.Migration):
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
("authentik.core", "authentik Core"),
],
default="",

View file

@ -76,7 +76,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
("authentik.core", "authentik Core"),
],
default="",

View file

@ -77,7 +77,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
("authentik.core", "authentik Core"),
],
default="",

View file

@ -74,7 +74,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",

View file

@ -75,7 +75,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",

View file

@ -76,7 +76,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",

View file

@ -81,7 +81,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.tenants", "authentik Tenants"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",

View file

@ -69,7 +69,7 @@ class Migration(migrations.Migration):
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.tenants", "authentik Tenants"),
("authentik.managed", "authentik Managed"),
("authentik.blueprints", "authentik Blueprints"),
("authentik.core", "authentik Core"),
],
default="",

View file

@ -1,5 +1,5 @@
"""OAuth2 Provider managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.blueprints.manager import EnsureExists, ObjectManager
from authentik.providers.oauth2.models import ScopeMapping
SCOPE_OPENID_EXPRESSION = """

View file

@ -24,6 +24,7 @@ from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.lib.generators import generate_id, generate_key
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
from authentik.providers.oauth2.apps import AuthentikProviderOAuth2Config
from authentik.providers.oauth2.constants import ACR_AUTHENTIK_DEFAULT
@ -335,7 +336,7 @@ class BaseGrantModel(models.Model):
abstract = True
class AuthorizationCode(ExpiringModel, BaseGrantModel):
class AuthorizationCode(SerializerModel, ExpiringModel, BaseGrantModel):
"""OAuth2 Authorization Code"""
code = models.CharField(max_length=255, unique=True, verbose_name=_("Code"))
@ -346,6 +347,12 @@ class AuthorizationCode(ExpiringModel, BaseGrantModel):
max_length=255, null=True, verbose_name=_("Code Challenge Method")
)
@property
def serializer(self) -> Serializer:
from authentik.providers.oauth2.api.tokens import ExpiringBaseGrantModelSerializer
return ExpiringBaseGrantModelSerializer
@property
def c_hash(self):
"""https://openid.net/specs/openid-connect-core-1_0.html#IDToken"""
@ -398,13 +405,19 @@ class IDToken:
return dic
class RefreshToken(ExpiringModel, BaseGrantModel):
class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
"""OAuth2 Refresh Token"""
access_token = models.TextField(verbose_name=_("Access Token"))
refresh_token = models.CharField(max_length=255, unique=True, verbose_name=_("Refresh Token"))
_id_token = models.TextField(verbose_name=_("ID Token"))
@property
def serializer(self) -> Serializer:
from authentik.providers.oauth2.api.tokens import ExpiringBaseGrantModelSerializer
return ExpiringBaseGrantModelSerializer
class Meta:
verbose_name = _("OAuth2 Token")
verbose_name_plural = _("OAuth2 Tokens")

View file

@ -5,10 +5,10 @@ from django.test import RequestFactory
from django.urls import reverse
from jwt import decode
from authentik.blueprints.manager import ObjectManager
from authentik.core.models import USER_ATTRIBUTE_SA, Application, Group, Token, TokenIntents
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.lib.generators import generate_id, generate_key
from authentik.managed.manager import ObjectManager
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
GRANT_TYPE_CLIENT_CREDENTIALS,

View file

@ -6,10 +6,10 @@ from django.test import RequestFactory
from django.urls import reverse
from jwt import decode
from authentik.blueprints.manager import ObjectManager
from authentik.core.models import Application, Group
from authentik.core.tests.utils import create_test_cert, create_test_flow
from authentik.lib.generators import generate_id, generate_key
from authentik.managed.manager import ObjectManager
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.constants import (
GRANT_TYPE_CLIENT_CREDENTIALS,

View file

@ -4,11 +4,11 @@ from dataclasses import asdict
from django.urls import reverse
from authentik.blueprints.manager import ObjectManager
from authentik.core.models import Application
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_id, generate_key
from authentik.managed.manager import ObjectManager
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken, ScopeMapping
from authentik.providers.oauth2.tests.utils import OAuthTestCase

View file

@ -1,5 +1,5 @@
"""OAuth2 Provider managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.blueprints.manager import EnsureExists, ObjectManager
from authentik.providers.oauth2.models import ScopeMapping
from authentik.providers.proxy.models import SCOPE_AK_PROXY

View file

@ -1,5 +1,5 @@
"""SAML Provider managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.blueprints.manager import EnsureExists, ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping
GROUP_EXPRESSION = """

View file

@ -4,11 +4,11 @@ from base64 import b64encode
from django.http.request import QueryDict
from django.test import RequestFactory, TestCase
from authentik.blueprints.manager import ObjectManager
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
from authentik.crypto.models import CertificateKeyPair
from authentik.events.models import Event, EventAction
from authentik.lib.tests.utils import get_request
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser

View file

@ -4,10 +4,10 @@ from base64 import b64encode
from django.test import RequestFactory, TestCase
from lxml import etree # nosec
from authentik.blueprints.manager import ObjectManager
from authentik.core.tests.utils import create_test_cert, create_test_flow
from authentik.lib.tests.utils import get_request
from authentik.lib.xml import lxml_from_string
from authentik.managed.manager import ObjectManager
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.request_parser import AuthNRequestParser

View file

@ -76,7 +76,7 @@ def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
def _get_startup_tasks() -> list[Callable]:
"""Get all tasks to be run on startup"""
from authentik.admin.tasks import clear_update_notifications
from authentik.managed.tasks import managed_reconcile
from authentik.blueprints.tasks import managed_reconcile
from authentik.outposts.tasks import outpost_controller_all, outpost_local_connection
from authentik.providers.proxy.tasks import proxy_set_defaults

View file

@ -122,7 +122,7 @@ INSTALLED_APPS = [
"authentik.stages.user_logout",
"authentik.stages.user_write",
"authentik.tenants",
"authentik.managed",
"authentik.blueprints",
"rest_framework",
"django_filters",
"drf_spectacular",

View file

@ -1,5 +1,5 @@
"""LDAP Source managed objects"""
from authentik.managed.manager import EnsureExists, ObjectManager
from authentik.blueprints.manager import EnsureExists, ObjectManager
from authentik.sources.ldap.models import LDAPPropertyMapping

View file

@ -4,9 +4,9 @@ from unittest.mock import Mock, PropertyMock, patch
from django.db.models import Q
from django.test import TestCase
from authentik.blueprints.manager import ObjectManager
from authentik.core.models import User
from authentik.lib.generators import generate_key
from authentik.managed.manager import ObjectManager
from authentik.sources.ldap.auth import LDAPBackend
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer

View file

@ -4,11 +4,11 @@ from unittest.mock import PropertyMock, patch
from django.db.models import Q
from django.test import TestCase
from authentik.blueprints.manager import ObjectManager
from authentik.core.models import Group, User
from authentik.core.tests.utils import create_test_admin_user
from authentik.events.models import Event, EventAction
from authentik.lib.generators import generate_key
from authentik.managed.manager import ObjectManager
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer

View file

@ -211,6 +211,14 @@ class UserOAuthSourceConnection(UserSourceConnection):
identifier = models.CharField(max_length=255)
access_token = models.TextField(blank=True, null=True, default=None)
@property
def serializer(self) -> Serializer:
from authentik.sources.oauth.api.source_connection import (
UserOAuthSourceConnectionSerializer,
)
return UserOAuthSourceConnectionSerializer
def save(self, *args, **kwargs):
self.access_token = self.access_token or None
super().save(*args, **kwargs)

View file

@ -7,7 +7,7 @@ from django.http.request import HttpRequest
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from rest_framework.fields import CharField
from rest_framework.serializers import BaseSerializer
from rest_framework.serializers import BaseSerializer, Serializer
from authentik.core.models import Source, UserSourceConnection
from authentik.core.types import UILoginButton, UserSettingSerializer
@ -99,6 +99,12 @@ class PlexSourceConnection(UserSourceConnection):
plex_token = models.TextField()
identifier = models.TextField()
@property
def serializer(self) -> Serializer:
from authentik.sources.plex.api.source_connection import PlexSourceConnectionSerializer
return PlexSourceConnectionSerializer
class Meta:
verbose_name = _("User Plex Source Connection")

View file

@ -7,11 +7,12 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from django_otp.models import Device
from duo_client.auth import Auth
from rest_framework.serializers import BaseSerializer
from rest_framework.serializers import BaseSerializer, Serializer
from authentik import __version__
from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.models import SerializerModel
class AuthenticatorDuoStage(ConfigurableStage, Stage):
@ -65,7 +66,7 @@ class AuthenticatorDuoStage(ConfigurableStage, Stage):
verbose_name_plural = _("Duo Authenticator Setup Stages")
class DuoDevice(Device):
class DuoDevice(SerializerModel, Device):
"""Duo Device for a single user"""
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
@ -73,9 +74,14 @@ class DuoDevice(Device):
# Connect to the stage to when validating access we know the API Credentials
stage = models.ForeignKey(AuthenticatorDuoStage, on_delete=models.CASCADE)
duo_user_id = models.TextField()
last_t = models.DateTimeField(auto_now=True)
@property
def serializer(self) -> Serializer:
from authentik.stages.authenticator_duo.api import DuoDeviceSerializer
return DuoDeviceSerializer
def __str__(self):
return self.name or str(self.user)

View file

@ -17,6 +17,7 @@ from twilio.rest import Client
from authentik.core.types import UserSettingSerializer
from authentik.events.models import Event, EventAction
from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.http import get_http_session
@ -163,7 +164,7 @@ def hash_phone_number(phone_number: str) -> str:
return "hash:" + sha256(phone_number.encode()).hexdigest()
class SMSDevice(SideChannelDevice):
class SMSDevice(SerializerModel, SideChannelDevice):
"""SMS Device"""
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
@ -184,6 +185,12 @@ class SMSDevice(SideChannelDevice):
"""Check if the phone number is hashed"""
return self.phone_number.startswith("hash:")
@property
def serializer(self) -> BaseSerializer:
from authentik.stages.authenticator_sms.api import SMSDeviceSerializer
return SMSDeviceSerializer
def verify_token(self, token):
valid = super().verify_token(token)
if valid:

View file

@ -7,12 +7,13 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views import View
from django_otp.models import Device
from rest_framework.serializers import BaseSerializer
from rest_framework.serializers import BaseSerializer, Serializer
from webauthn.helpers.base64url_to_bytes import base64url_to_bytes
from webauthn.helpers.structs import PublicKeyCredentialDescriptor
from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.models import SerializerModel
class UserVerification(models.TextChoices):
@ -113,7 +114,7 @@ class AuthenticateWebAuthnStage(ConfigurableStage, Stage):
verbose_name_plural = _("WebAuthn Authenticator Setup Stages")
class WebAuthnDevice(Device):
class WebAuthnDevice(SerializerModel, Device):
"""WebAuthn Device for a single user"""
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
@ -138,6 +139,12 @@ class WebAuthnDevice(Device):
self.last_t = now()
self.save()
@property
def serializer(self) -> Serializer:
from authentik.stages.authenticator_webauthn.api import WebAuthnDeviceSerializer
return WebAuthnDeviceSerializer
def __str__(self):
return self.name or str(self.user)

View file

@ -3,10 +3,11 @@
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 rest_framework.serializers import BaseSerializer, Serializer
from authentik.core.models import Application, ExpiringModel, User
from authentik.flows.models import Stage
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
@ -51,13 +52,19 @@ class ConsentStage(Stage):
verbose_name_plural = _("Consent Stages")
class UserConsent(ExpiringModel):
class UserConsent(SerializerModel, ExpiringModel):
"""Consent given by a user for an application"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
application = models.ForeignKey(Application, on_delete=models.CASCADE)
permissions = models.TextField(default="")
@property
def serializer(self) -> Serializer:
from authentik.stages.consent.api import UserConsentSerializer
return UserConsentSerializer
def __str__(self):
return f"User Consent {self.application} by {self.user}"

View file

@ -4,10 +4,11 @@ from uuid import uuid4
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 rest_framework.serializers import BaseSerializer, Serializer
from authentik.core.models import ExpiringModel, User
from authentik.flows.models import Stage
from authentik.lib.models import SerializerModel
class InvitationStage(Stage):
@ -47,7 +48,7 @@ class InvitationStage(Stage):
verbose_name_plural = _("Invitation Stages")
class Invitation(ExpiringModel):
class Invitation(SerializerModel, ExpiringModel):
"""Single-use invitation link"""
invite_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -66,6 +67,12 @@ class Invitation(ExpiringModel):
help_text=_("Optional fixed data to enforce on user enrollment."),
)
@property
def serializer(self) -> Serializer:
from authentik.stages.consent.api import UserConsentSerializer
return UserConsentSerializer
def __str__(self):
return f"Invitation {self.invite_uuid.hex} created by {self.created_by}"

View file

@ -1,40 +1,12 @@
# Generated by Django 3.2.3 on 2021-05-29 16:55
from django.apps.registry import Apps
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def create_default_tenant(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
Flow = apps.get_model("authentik_flows", "Flow")
Tenant = apps.get_model("authentik_tenants", "Tenant")
db_alias = schema_editor.connection.alias
default_authentication = (
Flow.objects.using(db_alias).filter(slug="default-authentication-flow").first()
)
default_invalidation = (
Flow.objects.using(db_alias).filter(slug="default-invalidation-flow").first()
)
tenant, _ = Tenant.objects.using(db_alias).update_or_create(
domain="authentik-default",
default=True,
defaults={
"flow_authentication": default_authentication,
"flow_invalidation": default_invalidation,
},
)
class Migration(migrations.Migration):
dependencies = [
("authentik_tenants", "0001_initial"),
("authentik_flows", "0008_default_flows"),
]
operations = [
migrations.RunPython(create_default_tenant),
]
operations = []

View file

@ -3,13 +3,15 @@ from uuid import uuid4
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
class Tenant(models.Model):
class Tenant(SerializerModel):
"""Single tenant"""
tenant_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -62,9 +64,14 @@ class Tenant(models.Model):
on_delete=models.SET_DEFAULT,
help_text=_(("Web Certificate used by the authentik Core webserver.")),
)
attributes = models.JSONField(default=dict, blank=True)
@property
def serializer(self) -> Serializer:
from authentik.tenants.api import TenantSerializer
return TenantSerializer
@property
def default_locale(self) -> str:
"""Get default locale"""

View file

@ -0,0 +1,76 @@
entries:
- attrs:
compatibility_mode: false
designation: stage_configuration
layout: stacked
name: Change Password
policy_engine_mode: all
title: Change password
identifiers:
slug: default-password-change
model: authentik_flows.flow
id: flow
- attrs:
order: 300
placeholder: Password
placeholder_expression: false
required: true
sub_text: ''
type: password
identifiers:
field_key: password
label: Password
id: prompt-field-password
model: authentik_stages_prompt.prompt
- attrs:
order: 301
placeholder: Password (repeat)
placeholder_expression: false
required: true
sub_text: ''
type: password
identifiers:
field_key: password_repeat
label: Password (repeat)
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
- attrs:
fields:
- !KeyOf prompt-field-password
- !KeyOf prompt-field-password-repeat
meta_model_name: authentik_stages_prompt.promptstage
validation_policies: []
identifiers:
name: default-password-change-prompt
id: default-password-change-prompt
model: authentik_stages_prompt.promptstage
- attrs:
create_users_as_inactive: false
create_users_group: null
meta_model_name: authentik_stages_user_write.userwritestage
user_path_template: ''
identifiers:
name: default-password-change-write
id: default-password-change-write
model: authentik_stages_user_write.userwritestage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-password-change-prompt
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 1
stage: !KeyOf default-password-change-write
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,102 @@
entries:
- attrs:
cache_count: 1
compatibility_mode: false
designation: authentication
layout: stacked
name: Welcome to authentik!
policy_engine_mode: all
title: Welcome to authentik!
identifiers:
slug: default-authentication-flow
model: authentik_flows.flow
id: flow
- attrs:
backends:
- authentik.core.auth.InbuiltBackend
- authentik.sources.ldap.auth.LDAPBackend
- authentik.core.auth.TokenBackend
- authentik.core.auth.TokenBackend
configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]]
failed_attempts_before_cancel: 5
meta_model_name: authentik_stages_password.passwordstage
identifiers:
name: default-authentication-password
id: default-authentication-password
model: authentik_stages_password.passwordstage
- attrs:
configuration_stages: []
device_classes:
- static
- totp
- webauthn
- duo
- sms
last_auth_threshold: seconds=0
meta_model_name: authentik_stages_authenticator_validate.authenticatorvalidatestage
not_configured_action: skip
identifiers:
name: default-authentication-mfa-validation
id: default-authentication-mfa-validation
model: authentik_stages_authenticator_validate.authenticatorvalidatestage
- attrs:
case_insensitive_matching: true
meta_model_name: authentik_stages_identification.identificationstage
show_matched_user: true
show_source_labels: false
sources: []
user_fields:
- email
- username
identifiers:
name: default-authentication-identification
id: default-authentication-identification
model: authentik_stages_identification.identificationstage
- attrs:
meta_model_name: authentik_stages_user_login.userloginstage
session_duration: seconds=0
identifiers:
name: default-authentication-login
id: default-authentication-login
model: authentik_stages_user_login.userloginstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 10
stage: !KeyOf default-authentication-identification
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 20
stage: !KeyOf default-authentication-password
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: false
identifiers:
order: 30
stage: !KeyOf default-authentication-mfa-validation
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 100
stage: !KeyOf default-authentication-login
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,30 @@
entries:
- attrs:
compatibility_mode: false
designation: invalidation
layout: stacked
name: Logout
policy_engine_mode: all
title: Default Invalidation Flow
identifiers:
pk: 46979d76-94d3-43b5-ad07-43e924c15d2c
slug: default-invalidation-flow
model: authentik_flows.flow
id: flow
- attrs:
meta_model_name: authentik_stages_user_logout.userlogoutstage
identifiers:
name: default-invalidation-logout
id: default-invalidation-logout
model: authentik_stages_user_logout.userlogoutstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-invalidation-logout
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,31 @@
entries:
- attrs:
compatibility_mode: false
designation: stage_configuration
layout: stacked
name: default-authenticator-static-setup
policy_engine_mode: any
title: Setup Static OTP Tokens
identifiers:
slug: default-authenticator-static-setup
model: authentik_flows.flow
id: flow
- attrs:
configure_flow: !KeyOf flow
meta_model_name: authentik_stages_authenticator_static.authenticatorstaticstage
token_count: 6
identifiers:
name: default-authenticator-static-setup
id: default-authenticator-static-setup
model: authentik_stages_authenticator_static.authenticatorstaticstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-authenticator-static-setup
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,31 @@
entries:
- attrs:
compatibility_mode: false
designation: stage_configuration
layout: stacked
name: default-authenticator-totp-setup
policy_engine_mode: any
title: Setup Two-Factor authentication
identifiers:
slug: default-authenticator-totp-setup
model: authentik_flows.flow
id: flow
- attrs:
configure_flow: !KeyOf flow
digits: 6
meta_model_name: authentik_stages_authenticator_totp.authenticatortotpstage
identifiers:
name: default-authenticator-totp-setup
id: default-authenticator-totp-setup
model: authentik_stages_authenticator_totp.authenticatortotpstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-authenticator-totp-setup
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,33 @@
entries:
- attrs:
compatibility_mode: false
designation: stage_configuration
layout: stacked
name: default-authenticator-webauthn-setup
policy_engine_mode: any
title: Setup WebAuthn
identifiers:
slug: default-authenticator-webauthn-setup
model: authentik_flows.flow
id: flow
- attrs:
authenticator_attachment: null
configure_flow: !KeyOf flow
meta_model_name: authentik_stages_authenticator_webauthn.authenticatewebauthnstage
resident_key_requirement: preferred
user_verification: preferred
identifiers:
name: default-authenticator-webauthn-setup
id: default-authenticator-webauthn-setup
model: authentik_stages_authenticator_webauthn.authenticatewebauthnstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-authenticator-webauthn-setup
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,31 @@
entries:
- attrs:
compatibility_mode: false
designation: authorization
layout: stacked
name: Authorize Application
policy_engine_mode: all
title: Redirecting to %(app)s
identifiers:
slug: default-provider-authorization-explicit-consent
model: authentik_flows.flow
id: flow
- attrs:
consent_expire_in: weeks=4
meta_model_name: authentik_stages_consent.consentstage
mode: always_require
identifiers:
name: default-provider-authorization-consent
id: default-provider-authorization-consent
model: authentik_stages_consent.consentstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-provider-authorization-consent
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,12 @@
entries:
- attrs:
compatibility_mode: false
designation: authorization
layout: stacked
name: Authorize Application
policy_engine_mode: all
title: Redirecting to %(app)s
identifiers:
slug: default-provider-authorization-implicit-consent
model: authentik_flows.flow
version: 1

View file

@ -0,0 +1,50 @@
entries:
- attrs:
compatibility_mode: false
designation: authentication
layout: stacked
name: Welcome to authentik!
policy_engine_mode: all
title: Welcome to authentik!
identifiers:
slug: default-source-authentication
model: authentik_flows.flow
id: flow
- attrs:
execution_logging: false
expression: |
# This policy ensures that this flow can only be used when the user
# is in a SSO Flow (meaning they come from an external IdP)
return ak_is_sso_flow
meta_model_name: authentik_policies_expression.expressionpolicy
identifiers:
name: default-source-authentication-if-sso
id: default-source-authentication-if-sso
model: authentik_policies_expression.expressionpolicy
- attrs:
meta_model_name: authentik_stages_user_login.userloginstage
session_duration: seconds=0
identifiers:
name: default-source-authentication-login
id: default-source-authentication-login
model: authentik_stages_user_login.userloginstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 0
stage: !KeyOf default-source-authentication-login
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
enabled: true
negate: false
timeout: 30
identifiers:
order: 0
policy: !KeyOf default-source-authentication-if-sso
target: !KeyOf flow
model: authentik_policies.policybinding
version: 1

View file

@ -0,0 +1,121 @@
entries:
- attrs:
compatibility_mode: false
designation: enrollment
layout: stacked
name: Welcome to authentik! Please select a username.
policy_engine_mode: all
title: Welcome to authentik! Please select a username.
identifiers:
slug: default-source-enrollment
model: authentik_flows.flow
id: flow
- attrs:
order: 100
placeholder: Username
placeholder_expression: false
required: true
sub_text: ''
type: text
identifiers:
field_key: username
label: Username
id: prompt-field-username
model: authentik_stages_prompt.prompt
- attrs:
execution_logging: false
expression: |
# Check if we''ve not been given a username by the external IdP
# and trigger the enrollment flow
return ''username'' not in context.get(''prompt_data'', {})
meta_model_name: authentik_policies_expression.expressionpolicy
identifiers:
name: default-source-enrollment-if-username
id: default-source-enrollment-if-username
model: authentik_policies_expression.expressionpolicy
- attrs:
execution_logging: false
expression: |
# This policy ensures that this flow can only be used when the user
# is in a SSO Flow (meaning they come from an external IdP)
return ak_is_sso_flow
meta_model_name: authentik_policies_expression.expressionpolicy
identifiers:
name: default-source-enrollment-if-sso
id: default-source-enrollment-if-sso
model: authentik_policies_expression.expressionpolicy
- attrs:
meta_model_name: authentik_stages_user_login.userloginstage
session_duration: seconds=0
identifiers:
name: default-source-enrollment-login
id: default-source-enrollment-login
model: authentik_stages_user_login.userloginstage
- attrs:
fields:
- !KeyOf prompt-field-username
meta_model_name: authentik_stages_prompt.promptstage
validation_policies: []
identifiers:
name: default-source-enrollment-prompt
id: default-source-enrollment-prompt
model: authentik_stages_prompt.promptstage
- attrs:
create_users_as_inactive: false
create_users_group: null
meta_model_name: authentik_stages_user_write.userwritestage
user_path_template: ''
identifiers:
name: default-source-enrollment-write
id: default-source-enrollment-write
model: authentik_stages_user_write.userwritestage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: true
identifiers:
order: 0
stage: !KeyOf default-source-enrollment-prompt
target: !KeyOf flow
id: prompt-binding
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 1
stage: !KeyOf default-source-enrollment-write
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: all
re_evaluate_policies: false
identifiers:
order: 2
stage: !KeyOf default-source-enrollment-login
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
enabled: true
negate: false
timeout: 30
identifiers:
order: 0
policy: !KeyOf default-source-enrollment-if-sso
target: !KeyOf flow
model: authentik_policies.policybinding
- attrs:
enabled: true
negate: false
timeout: 30
identifiers:
order: 0
policy: !KeyOf default-source-enrollment-if-username
target: !KeyOf prompt-binding
model: authentik_policies.policybinding
version: 1

View file

@ -0,0 +1,12 @@
entries:
- attrs:
compatibility_mode: false
designation: stage_configuration
layout: stacked
name: Pre-Authentication
policy_engine_mode: any
title: ''
identifiers:
slug: default-source-pre-authentication
model: authentik_flows.flow
version: 1

View file

@ -0,0 +1,157 @@
entries:
- attrs:
compatibility_mode: false
designation: stage_configuration
layout: stacked
name: Update your info
policy_engine_mode: any
title: ''
identifiers:
slug: default-user-settings-flow
model: authentik_flows.flow
id: flow
- attrs:
order: 200
placeholder: |
try:
return user.username
except:
return ''
placeholder_expression: true
required: true
sub_text: ''
type: text
identifiers:
field_key: username
label: Username
id: prompt-field-username
model: authentik_stages_prompt.prompt
- attrs:
order: 201
placeholder: |
try:
return user.name
except:
return ''
placeholder_expression: true
required: true
sub_text: ''
type: text
identifiers:
field_key: name
label: Name
id: prompt-field-name
model: authentik_stages_prompt.prompt
- attrs:
order: 202
placeholder: |
try:
return user.email
except:
return ''
placeholder_expression: true
required: true
sub_text: ''
type: email
identifiers:
field_key: email
label: Email
id: prompt-field-email
model: authentik_stages_prompt.prompt
- attrs:
order: 203
placeholder: |
try:
return user.attributes.get("settings", {}).get("locale", "")
except:
return ''
placeholder_expression: true
required: true
sub_text: ''
type: ak-locale
identifiers:
field_key: attributes.settings.locale
label: Locale
id: prompt-field-locale
model: authentik_stages_prompt.prompt
- attrs:
execution_logging: false
expression: |
from authentik.lib.config import CONFIG
from authentik.core.models import (
USER_ATTRIBUTE_CHANGE_EMAIL,
USER_ATTRIBUTE_CHANGE_NAME,
USER_ATTRIBUTE_CHANGE_USERNAME
)
prompt_data = request.context.get("prompt_data")
if not request.user.group_attributes(request.http_request).get(
USER_ATTRIBUTE_CHANGE_EMAIL, CONFIG.y_bool("default_user_change_email", True)
):
if prompt_data.get("email") != request.user.email:
ak_message("Not allowed to change email address.")
return False
if not request.user.group_attributes(request.http_request).get(
USER_ATTRIBUTE_CHANGE_NAME, CONFIG.y_bool("default_user_change_name", True)
):
if prompt_data.get("name") != request.user.name:
ak_message("Not allowed to change name.")
return False
if not request.user.group_attributes(request.http_request).get(
USER_ATTRIBUTE_CHANGE_USERNAME, CONFIG.y_bool("default_user_change_username", True)
):
if prompt_data.get("username") != request.user.username:
ak_message("Not allowed to change username.")
return False
return True
meta_model_name: authentik_policies_expression.expressionpolicy
name: default-user-settings-authorization
identifiers:
name: default-user-settings-authorization
model: authentik_policies_expression.expressionpolicy
- attrs:
create_users_as_inactive: false
create_users_group: null
meta_model_name: authentik_stages_user_write.userwritestage
user_path_template: ''
identifiers:
name: default-user-settings-write
id: default-user-settings-write
model: authentik_stages_user_write.userwritestage
- attrs:
fields:
- !KeyOf prompt-field-username
- !KeyOf prompt-field-name
- !KeyOf prompt-field-email
- !KeyOf prompt-field-locale
meta_model_name: authentik_stages_prompt.promptstage
validation_policies:
- !KeyOf default-user-settings-authorization
identifiers:
name: default-user-settings
id: default-user-settings
model: authentik_stages_prompt.promptstage
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: false
identifiers:
order: 20
stage: !KeyOf default-user-settings
target: !KeyOf flow
model: authentik_flows.flowstagebinding
- attrs:
evaluate_on_plan: true
invalid_response_action: retry
policy_engine_mode: any
re_evaluate_policies: false
identifiers:
order: 100
stage: !KeyOf default-user-settings-write
target: !KeyOf flow
model: authentik_flows.flowstagebinding
version: 1

View file

@ -0,0 +1,9 @@
version: 1
entries:
- attrs:
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
identifiers:
domain: authentik-default
default: True
model: authentik_tenants.Tenant

View file

@ -1,119 +1,116 @@
version: 1
entries:
- identifiers:
pk: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
slug: default-enrollment-flow
model: authentik_flows.flow
id: flow
attrs:
name: Default enrollment Flow
title: Welcome to authentik!
designation: enrollment
- identifiers:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt
attrs:
field_key: username
label: Username
id: prompt-field-username
model: authentik_stages_prompt.prompt
attrs:
type: username
required: true
placeholder: Username
order: 0
- identifiers:
pk: 7db91ee8-4290-4e08-8d39-63f132402515
model: authentik_stages_prompt.prompt
attrs:
field_key: password
label: Password
id: prompt-field-password
model: authentik_stages_prompt.prompt
attrs:
type: password
required: true
placeholder: Password
order: 0
- identifiers:
pk: d30b5eb4-7787-4072-b1ba-65b46e928920
model: authentik_stages_prompt.prompt
attrs:
field_key: password_repeat
label: Password (repeat)
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
attrs:
type: password
required: true
placeholder: Password (repeat)
order: 1
- identifiers:
pk: f78d977a-efa6-4cc2-9a0f-2621a9fd94d2
model: authentik_stages_prompt.prompt
attrs:
field_key: name
label: Name
id: prompt-field-name
model: authentik_stages_prompt.prompt
attrs:
type: text
required: true
placeholder: Name
order: 0
- identifiers:
pk: 1ff91927-e33d-4615-95b0-c258e5f0df62
model: authentik_stages_prompt.prompt
attrs:
field_key: email
label: Email
id: prompt-field-email
model: authentik_stages_prompt.prompt
attrs:
type: email
required: true
placeholder: Email
order: 1
- identifiers:
pk: 6c342b94-790d-425a-ae31-6196b6570722
name: default-enrollment-prompt-second
id: default-enrollment-prompt-second
model: authentik_stages_prompt.promptstage
attrs:
fields:
- f78d977a-efa6-4cc2-9a0f-2621a9fd94d2
- 1ff91927-e33d-4615-95b0-c258e5f0df62
- !KeyOf prompt-field-name
- !KeyOf prompt-field-email
- identifiers:
pk: 20375f30-7fa7-4562-8f6e-0f61889f2963
name: default-enrollment-prompt-first
id: default-enrollment-prompt-first
model: authentik_stages_prompt.promptstage
attrs:
fields:
- cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
- 7db91ee8-4290-4e08-8d39-63f132402515
- d30b5eb4-7787-4072-b1ba-65b46e928920
- !KeyOf prompt-field-username
- !KeyOf prompt-field-password
- !KeyOf prompt-field-password-repeat
- identifiers:
pk: 77090897-eb3f-40db-81e6-b4074b1998c4
pk: !KeyOf default-enrollment-user-login
name: default-enrollment-user-login
id: default-enrollment-user-login
model: authentik_stages_user_login.userloginstage
attrs:
session_duration: seconds=0
- identifiers:
pk: a4090add-f483-4ac6-8917-10b493ef843e
name: default-enrollment-user-write
id: default-enrollment-user-write
model: authentik_stages_user_write.userwritestage
attrs: {}
- identifiers:
pk: 34e1e7d5-8eed-4549-bc7a-305069ff7df0
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 20375f30-7fa7-4562-8f6e-0f61889f2963
target: !KeyOf flow
stage: !KeyOf default-enrollment-prompt-first
order: 10
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: e40467a6-3052-488c-a1b5-1ad7a80fe7b3
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 6c342b94-790d-425a-ae31-6196b6570722
target: !KeyOf flow
stage: !KeyOf default-enrollment-prompt-second
order: 11
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 76bc594e-2715-49ab-bd40-994abd9a7b70
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: a4090add-f483-4ac6-8917-10b493ef843e
target: !KeyOf flow
stage: !KeyOf default-enrollment-user-write
order: 20
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 2f324f6d-7646-4108-a6e2-e7f90985477f
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 77090897-eb3f-40db-81e6-b4074b1998c4
target: !KeyOf flow
stage: !KeyOf default-enrollment-user-login
order: 100
model: authentik_flows.flowstagebinding
attrs:

View file

@ -1,66 +1,66 @@
version: 1
entries:
- identifiers:
pk: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
slug: default-enrollment-flow
id: flow
model: authentik_flows.flow
attrs:
name: Default enrollment Flow
title: Welcome to authentik!
designation: enrollment
- identifiers:
pk: cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
model: authentik_stages_prompt.prompt
attrs:
field_key: username
label: Username
id: prompt-field-username
model: authentik_stages_prompt.prompt
attrs:
type: username
required: true
placeholder: Username
order: 0
- identifiers:
pk: 7db91ee8-4290-4e08-8d39-63f132402515
model: authentik_stages_prompt.prompt
attrs:
field_key: password
label: Password
id: prompt-field-password
model: authentik_stages_prompt.prompt
attrs:
type: password
required: true
placeholder: Password
order: 0
- identifiers:
pk: d30b5eb4-7787-4072-b1ba-65b46e928920
model: authentik_stages_prompt.prompt
attrs:
field_key: password_repeat
label: Password (repeat)
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
attrs:
type: password
required: true
placeholder: Password (repeat)
order: 1
- identifiers:
pk: f78d977a-efa6-4cc2-9a0f-2621a9fd94d2
model: authentik_stages_prompt.prompt
attrs:
field_key: name
label: Name
id: prompt-field-name
model: authentik_stages_prompt.prompt
attrs:
type: text
required: true
placeholder: Name
order: 0
- identifiers:
pk: 1ff91927-e33d-4615-95b0-c258e5f0df62
model: authentik_stages_prompt.prompt
attrs:
field_key: email
label: Email
id: prompt-field-email
model: authentik_stages_prompt.prompt
attrs:
type: email
required: true
placeholder: Email
order: 1
- identifiers:
pk: 096e6282-6b30-4695-bd03-3b143eab5580
name: default-enrollment-email-verification
id: default-enrollment-email-verification
model: authentik_stages_email.emailstage
attrs:
use_global_settings: true
@ -76,70 +76,65 @@ entries:
template: email/account_confirmation.html
activate_user_on_success: true
- identifiers:
pk: 6c342b94-790d-425a-ae31-6196b6570722
name: default-enrollment-prompt-second
id: default-enrollment-prompt-second
model: authentik_stages_prompt.promptstage
attrs:
fields:
- f78d977a-efa6-4cc2-9a0f-2621a9fd94d2
- 1ff91927-e33d-4615-95b0-c258e5f0df62
- !KeyOf prompt-field-name
- !KeyOf prompt-field-email
- identifiers:
pk: 20375f30-7fa7-4562-8f6e-0f61889f2963
name: default-enrollment-prompt-first
id: default-enrollment-prompt-first
model: authentik_stages_prompt.promptstage
attrs:
fields:
- cb954fd4-65a5-4ad9-b1ee-180ee9559cf4
- 7db91ee8-4290-4e08-8d39-63f132402515
- d30b5eb4-7787-4072-b1ba-65b46e928920
- !KeyOf prompt-field-username
- !KeyOf prompt-field-password
- !KeyOf prompt-field-password-repeat
- identifiers:
pk: 77090897-eb3f-40db-81e6-b4074b1998c4
name: default-enrollment-user-login
id: default-enrollment-user-login
model: authentik_stages_user_login.userloginstage
attrs:
session_duration: seconds=0
- identifiers:
pk: a4090add-f483-4ac6-8917-10b493ef843e
name: default-enrollment-user-write
id: default-enrollment-user-write
model: authentik_stages_user_write.userwritestage
attrs:
create_users_as_inactive: true
- identifiers:
pk: 34e1e7d5-8eed-4549-bc7a-305069ff7df0
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 20375f30-7fa7-4562-8f6e-0f61889f2963
target: !KeyOf flow
stage: !KeyOf default-enrollment-prompt-first
order: 10
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: e40467a6-3052-488c-a1b5-1ad7a80fe7b3
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 6c342b94-790d-425a-ae31-6196b6570722
target: !KeyOf flow
stage: !KeyOf default-enrollment-prompt-second
order: 11
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 76bc594e-2715-49ab-bd40-994abd9a7b70
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: a4090add-f483-4ac6-8917-10b493ef843e
target: !KeyOf flow
stage: !KeyOf default-enrollment-user-write
order: 20
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 1db34a14-8985-4184-b5c9-254cd585d94f
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 096e6282-6b30-4695-bd03-3b143eab5580
target: !KeyOf flow
stage: !KeyOf default-enrollment-email-verification
order: 30
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 2f324f6d-7646-4108-a6e2-e7f90985477f
target: 773c6673-e4a2-423f-8d32-95b7b4a41cf3
stage: 77090897-eb3f-40db-81e6-b4074b1998c4
target: !KeyOf flow
stage: !KeyOf default-enrollment-user-login
order: 40
model: authentik_flows.flowstagebinding
attrs:

View file

@ -2,29 +2,29 @@ version: 1
entries:
- identifiers:
slug: default-authentication-flow
pk: 563ece21-e9a4-47e5-a264-23ffd923e393
model: authentik_flows.flow
id: flow
attrs:
name: Default Authentication Flow
title: Welcome to authentik!
designation: authentication
- identifiers:
pk: 7db93f1e-788b-4af6-8dc6-5cdeb59d8be7
name: test-not-app-password
id: test-not-app-password
model: authentik_policies_expression.expressionpolicy
attrs:
name: test-not-app-password
execution_logging: false
bound_to: 1
expression: return context["auth_method"] != "app_password"
expression: |
return context["auth_method"] != "app_password"
- identifiers:
pk: 69d41125-3987-499b-8d74-ef27b54b88c8
name: default-authentication-login
id: default-authentication-login
model: authentik_stages_user_login.userloginstage
attrs:
session_duration: seconds=0
- identifiers:
pk: 5f594f27-0def-488d-9855-fe604eb13de5
name: default-authentication-identification
id: default-authentication-identification
model: authentik_stages_identification.identificationstage
attrs:
user_fields:
@ -34,13 +34,14 @@ entries:
enrollment_flow: null
recovery_flow: null
- identifiers:
pk: 37f709c3-8817-45e8-9a93-80a925d293c2
name: default-authentication-flow-mfa
id: default-authentication-flow-mfa
model: authentik_stages_authenticator_validate.AuthenticatorValidateStage
attrs: {}
- identifiers:
pk: d8affa62-500c-4c5c-a01f-5835e1ffdf40
pk: !KeyOf default-authentication-password
name: default-authentication-password
id: default-authentication-password
model: authentik_stages_password.passwordstage
attrs:
backends:
@ -48,44 +49,40 @@ entries:
- authentik.core.auth.TokenBackend
- authentik.sources.ldap.auth.LDAPBackend
- identifiers:
pk: a3056482-b692-4e3a-93f1-7351c6a351c7
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: 5f594f27-0def-488d-9855-fe604eb13de5
target: !KeyOf flow
stage: !KeyOf default-authentication-identification
order: 10
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 4e8538cf-3e18-4a68-82ae-6df6725fa2e6
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: d8affa62-500c-4c5c-a01f-5835e1ffdf40
target: !KeyOf flow
stage: !KeyOf default-authentication-password
order: 20
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 688aec6f-5622-42c6-83a5-d22072d7e798
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: 37f709c3-8817-45e8-9a93-80a925d293c2
target: !KeyOf flow
stage: !KeyOf default-authentication-flow-mfa
order: 30
model: authentik_flows.flowstagebinding
id: flow-binding-mfa
attrs:
evaluate_on_plan: false
re_evaluate_policies: true
policy_engine_mode: any
invalid_response_action: retry
- identifiers:
pk: f3fede3a-a9b5-4232-9ec7-be7ff4194b27
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: 69d41125-3987-499b-8d74-ef27b54b88c8
target: !KeyOf flow
stage: !KeyOf default-authentication-login
order: 100
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 6e40ae4d-a4ed-4bd7-a784-27b1fe5859d2
policy: 7db93f1e-788b-4af6-8dc6-5cdeb59d8be7
target: 688aec6f-5622-42c6-83a5-d22072d7e798
policy: !KeyOf test-not-app-password
target: !KeyOf flow-binding-mfa
order: 0
model: authentik_policies.policybinding
attrs:

View file

@ -2,7 +2,7 @@ version: 1
entries:
- identifiers:
slug: default-authentication-flow
pk: 563ece21-e9a4-47e5-a264-23ffd923e393
id: flow
model: authentik_flows.flow
attrs:
name: Default Authentication Flow
@ -10,20 +10,20 @@ entries:
designation: authentication
- identifiers:
name: default-authentication-login
pk: 69d41125-3987-499b-8d74-ef27b54b88c8
id: default-authentication-login
model: authentik_stages_user_login.userloginstage
attrs:
session_duration: seconds=0
- identifiers:
name: default-authentication-flow-captcha
pk: a368cafc-1494-45e9-b75b-b5e7ac2bd3e4
id: default-authentication-flow-captcha
model: authentik_stages_captcha.captchastage
attrs:
public_key: 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
private_key: 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
- identifiers:
name: default-authentication-identification
pk: 5f594f27-0def-488d-9855-fe604eb13de5
id: default-authentication-identification
model: authentik_stages_identification.identificationstage
attrs:
user_fields:
@ -34,7 +34,7 @@ entries:
recovery_flow: null
- identifiers:
name: default-authentication-password
pk: d8affa62-500c-4c5c-a01f-5835e1ffdf40
id: default-authentication-password
model: authentik_stages_password.passwordstage
attrs:
backends:
@ -42,50 +42,46 @@ entries:
- authentik.core.auth.TokenBackend
- authentik.sources.ldap.auth.LDAPBackend
- identifiers:
pk: a3056482-b692-4e3a-93f1-7351c6a351c7
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: 5f594f27-0def-488d-9855-fe604eb13de5
target: !KeyOf flow
stage: !KeyOf default-authentication-identification
order: 10
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 4e8538cf-3e18-4a68-82ae-6df6725fa2e6
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: d8affa62-500c-4c5c-a01f-5835e1ffdf40
target: !KeyOf flow
stage: !KeyOf default-authentication-password
order: 20
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 3bcd6af0-48a6-4e18-87f3-d251a1a58226
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: a368cafc-1494-45e9-b75b-b5e7ac2bd3e4
target: !KeyOf flow
stage: !KeyOf default-authentication-flow-captcha
order: 30
id: flow-binding-captcha
model: authentik_flows.flowstagebinding
attrs:
evaluate_on_plan: false
re_evaluate_policies: true
- identifiers:
pk: f3fede3a-a9b5-4232-9ec7-be7ff4194b27
target: 563ece21-e9a4-47e5-a264-23ffd923e393
stage: 69d41125-3987-499b-8d74-ef27b54b88c8
target: !KeyOf flow
stage: !KeyOf default-authentication-login
order: 100
model: authentik_flows.flowstagebinding
attrs:
re_evaluate_policies: false
- identifiers:
pk: 688c9890-47ad-4327-a9e5-380e88d34be5
name: default-authentication-flow-conditional-captcha
id: default-authentication-flow-conditional-captcha
model: authentik_policies_reputation.reputationpolicy
attrs:
name: default-authentication-flow-conditional-captcha
check_ip: true
check_username: true
threshold: -5
- identifiers:
pk: 02e4d220-3448-44db-822e-c5255cf7c250
policy: 688c9890-47ad-4327-a9e5-380e88d34be5
target: 3bcd6af0-48a6-4e18-87f3-d251a1a58226
policy: !KeyOf default-authentication-flow-conditional-captcha
target: !KeyOf flow-binding-captcha
order: 0
model: authentik_policies.policybinding
attrs:

View file

@ -1,8 +1,8 @@
version: 1
entries:
- identifiers:
pk: a5993183-89c0-43d2-a7f4-ddffb17baba7
slug: default-recovery-flow
id: flow
model: authentik_flows.flow
attrs:
name: Default recovery flow
@ -13,11 +13,11 @@ entries:
compatibility_mode: false
layout: stacked
- identifiers:
pk: 7db91ee8-4290-4e08-8d39-63f132402515
model: authentik_stages_prompt.prompt
attrs:
field_key: password
label: Password
id: prompt-field-password
model: authentik_stages_prompt.prompt
attrs:
type: password
required: true
placeholder: Password
@ -25,11 +25,11 @@ entries:
sub_text: ""
placeholder_expression: false
- identifiers:
pk: d30b5eb4-7787-4072-b1ba-65b46e928920
model: authentik_stages_prompt.prompt
attrs:
field_key: password_repeat
label: Password (repeat)
id: prompt-field-password-repeat
model: authentik_stages_prompt.prompt
attrs:
type: password
required: true
placeholder: Password (repeat)
@ -37,24 +37,16 @@ entries:
sub_text: ""
placeholder_expression: false
- identifiers:
pk: 1c5709ae-1b3e-413a-a117-260ab509bf5c
name: default-recovery-skip-if-restored
id: default-recovery-skip-if-restored
model: authentik_policies_expression.expressionpolicy
attrs:
name: default-recovery-skip-if-restored
execution_logging: false
bound_to: 2
expression: return request.context.get('is_restored', False)
expression: |
return request.context.get('is_restored', False)
- identifiers:
pk: 1c5709ae-1b3e-413a-a117-260ab509bf5c
model: authentik_policies_expression.expressionpolicy
attrs:
name: default-recovery-skip-if-restored
execution_logging: false
bound_to: 2
expression: return request.context.get('is_restored', False)
- identifiers:
pk: 4ac5719f-32c0-441c-8a7e-33c5ea0db7da
name: default-recovery-email
id: default-recovery-email
model: authentik_stages_email.emailstage
attrs:
use_global_settings: true
@ -70,16 +62,16 @@ entries:
template: email/password_reset.html
activate_user_on_success: true
- identifiers:
pk: 68b25ad5-318a-496e-95a7-cf4d94247f0d
name: default-recovery-user-write
id: default-recovery-user-write
model: authentik_stages_user_write.userwritestage
attrs:
create_users_as_inactive: false
create_users_group: null
user_path_template: ""
- identifiers:
pk: 94843ef6-28fe-4939-bd61-cd46bb34f1de
name: default-recovery-identification
id: default-recovery-identification
model: authentik_stages_identification.identificationstage
attrs:
user_fields:
@ -94,37 +86,37 @@ entries:
sources: []
show_source_labels: false
- identifiers:
pk: e74230b2-82bc-4843-8b18-2c3a66a62d57
name: default-recovery-user-login
id: default-recovery-user-login
model: authentik_stages_user_login.userloginstage
attrs:
session_duration: seconds=0
- identifiers:
pk: fa2d8d65-1809-4dcc-bdc0-56266e0f7971
name: Change your password
name: stages-prompt-password
model: authentik_stages_prompt.promptstage
attrs:
fields:
- 7db91ee8-4290-4e08-8d39-63f132402515
- d30b5eb4-7787-4072-b1ba-65b46e928920
- !KeyOf prompt-field-password
- !KeyOf prompt-field-password-repeat
validation_policies: []
- identifiers:
pk: 7af7558e-2196-4b9f-a08e-d38420b7cfbb
target: a5993183-89c0-43d2-a7f4-ddffb17baba7
stage: 94843ef6-28fe-4939-bd61-cd46bb34f1de
target: !KeyOf flow
stage: !KeyOf default-recovery-identification
order: 10
model: authentik_flows.flowstagebinding
id: flow-binding-identification
attrs:
evaluate_on_plan: true
re_evaluate_policies: true
policy_engine_mode: any
invalid_response_action: retry
- identifiers:
pk: 29446fd6-dd93-4e92-9830-2d81debad5ae
target: a5993183-89c0-43d2-a7f4-ddffb17baba7
stage: 4ac5719f-32c0-441c-8a7e-33c5ea0db7da
target: !KeyOf flow
stage: !KeyOf default-recovery-email
order: 20
model: authentik_flows.flowstagebinding
id: flow-binding-email
attrs:
evaluate_on_plan: true
re_evaluate_policies: true
@ -132,8 +124,8 @@ entries:
invalid_response_action: retry
- identifiers:
pk: 1219d06e-2c06-4c5b-a162-78e3959c6cf0
target: a5993183-89c0-43d2-a7f4-ddffb17baba7
stage: fa2d8d65-1809-4dcc-bdc0-56266e0f7971
target: !KeyOf flow
stage: !KeyOf stages-prompt-password
order: 30
model: authentik_flows.flowstagebinding
attrs:
@ -142,9 +134,8 @@ entries:
policy_engine_mode: any
invalid_response_action: retry
- identifiers:
pk: 66de86ba-0707-46a0-8475-ff2e260d6935
target: a5993183-89c0-43d2-a7f4-ddffb17baba7
stage: 68b25ad5-318a-496e-95a7-cf4d94247f0d
target: !KeyOf flow
stage: !KeyOf default-recovery-user-write
order: 40
model: authentik_flows.flowstagebinding
attrs:
@ -153,9 +144,8 @@ entries:
policy_engine_mode: any
invalid_response_action: retry
- identifiers:
pk: 9cec2334-d4a2-4895-a2b2-bc5ae4e9639a
target: a5993183-89c0-43d2-a7f4-ddffb17baba7
stage: e74230b2-82bc-4843-8b18-2c3a66a62d57
target: !KeyOf flow
stage: !KeyOf default-recovery-user-login
order: 100
model: authentik_flows.flowstagebinding
attrs:
@ -164,9 +154,8 @@ entries:
policy_engine_mode: any
invalid_response_action: retry
- identifiers:
pk: 95aad215-8729-4177-953d-41ffbe86239e
policy: 1c5709ae-1b3e-413a-a117-260ab509bf5c
target: 7af7558e-2196-4b9f-a08e-d38420b7cfbb
policy: !KeyOf default-recovery-skip-if-restored
target: !KeyOf flow-binding-identification
order: 0
model: authentik_policies.policybinding
attrs:
@ -174,9 +163,8 @@ entries:
enabled: true
timeout: 30
- identifiers:
pk: a5454cbc-d2e4-403a-84af-6af999990b12
policy: 1c5709ae-1b3e-413a-a117-260ab509bf5c
target: 29446fd6-dd93-4e92-9830-2d81debad5ae
policy: !KeyOf default-recovery-skip-if-restored
target: !KeyOf flow-binding-email
order: 0
model: authentik_policies.policybinding
attrs:

View file

@ -1,22 +1,21 @@
version: 1
entries:
- identifiers:
pk: 59a576ce-2f23-4a63-b63a-d18dc7e550f5
slug: default-unenrollment-flow
model: authentik_flows.flow
id: flow
attrs:
name: Default unenrollment flow
title: Delete your account
designation: unenrollment
- identifiers:
pk: c62ac2a4-2735-4a0f-abd0-8523d68c1209
name: default-unenrollment-user-delete
id: default-unenrollment-user-delete
model: authentik_stages_user_delete.userdeletestage
attrs: {}
- identifiers:
pk: eb9aff2b-b95d-40b3-ad08-233aa77bbcf3
target: 59a576ce-2f23-4a63-b63a-d18dc7e550f5
stage: c62ac2a4-2735-4a0f-abd0-8523d68c1209
target: !KeyOf flow
stage: !KeyOf default-unenrollment-user-delete
order: 10
model: authentik_flows.flowstagebinding
attrs:

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-28 19:11+0000\n"
"POT-Creation-Date: 2022-07-31 14:22+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -31,6 +31,18 @@ msgstr ""
msgid "Validation Error"
msgstr ""
#: authentik/blueprints/models.py:18
msgid "Managed by authentik"
msgstr ""
#: authentik/blueprints/models.py:69
msgid "Blueprint Instance"
msgstr ""
#: authentik/blueprints/models.py:70
msgid "Blueprint Instances"
msgstr ""
#: authentik/core/api/providers.py:89
msgid "SAML Provider from Metadata"
msgstr ""
@ -55,95 +67,95 @@ msgstr ""
msgid "Users added to this group will be superusers."
msgstr ""
#: authentik/core/models.py:146
#: authentik/core/models.py:152
msgid "User's display name."
msgstr ""
#: authentik/core/models.py:239 authentik/providers/oauth2/models.py:321
#: authentik/core/models.py:251 authentik/providers/oauth2/models.py:322
msgid "User"
msgstr ""
#: authentik/core/models.py:240
#: authentik/core/models.py:252
msgid "Users"
msgstr ""
#: authentik/core/models.py:251
#: authentik/core/models.py:263
msgid "Flow used when authorizing this provider."
msgstr ""
#: authentik/core/models.py:284
#: authentik/core/models.py:296
msgid "Application's display Name."
msgstr ""
#: authentik/core/models.py:285
#: authentik/core/models.py:297
msgid "Internal application name, used in URLs."
msgstr ""
#: authentik/core/models.py:297
#: authentik/core/models.py:309
msgid "Open launch URL in a new browser tab or window."
msgstr ""
#: authentik/core/models.py:356
#: authentik/core/models.py:374
msgid "Application"
msgstr ""
#: authentik/core/models.py:357
#: authentik/core/models.py:375
msgid "Applications"
msgstr ""
#: authentik/core/models.py:363
#: authentik/core/models.py:381
msgid "Use the source-specific identifier"
msgstr ""
#: authentik/core/models.py:371
#: authentik/core/models.py:389
msgid ""
"Use the user's email address, but deny enrollment when the email address "
"already exists."
msgstr ""
#: authentik/core/models.py:380
#: authentik/core/models.py:398
msgid ""
"Use the user's username, but deny enrollment when the username already "
"exists."
msgstr ""
#: authentik/core/models.py:387
#: authentik/core/models.py:405
msgid "Source's display Name."
msgstr ""
#: authentik/core/models.py:388
#: authentik/core/models.py:406
msgid "Internal source name, used in URLs."
msgstr ""
#: authentik/core/models.py:401
#: authentik/core/models.py:419
msgid "Flow to use when authenticating existing users."
msgstr ""
#: authentik/core/models.py:410
#: authentik/core/models.py:428
msgid "Flow to use when enrolling new users."
msgstr ""
#: authentik/core/models.py:560
#: authentik/core/models.py:589
msgid "Token"
msgstr ""
#: authentik/core/models.py:561
#: authentik/core/models.py:590
msgid "Tokens"
msgstr ""
#: authentik/core/models.py:604
#: authentik/core/models.py:633
msgid "Property Mapping"
msgstr ""
#: authentik/core/models.py:605
#: authentik/core/models.py:634
msgid "Property Mappings"
msgstr ""
#: authentik/core/models.py:641
#: authentik/core/models.py:670
msgid "Authenticated Session"
msgstr ""
#: authentik/core/models.py:642
#: authentik/core/models.py:671
msgid "Authenticated Sessions"
msgstr ""
@ -166,12 +178,12 @@ msgstr ""
msgid "Go to home"
msgstr ""
#: authentik/core/templates/if/admin.html:18
#: authentik/core/templates/if/admin.html:24
#: authentik/core/templates/if/flow.html:37
#: authentik/core/templates/if/flow.html:43
#: authentik/core/templates/if/user.html:18
#: authentik/core/templates/if/admin.html:30
#: authentik/core/templates/if/flow.html:38
#: authentik/core/templates/if/flow.html:44
#: authentik/core/templates/if/user.html:24
#: authentik/core/templates/if/user.html:30
msgid "Loading..."
msgstr ""
@ -227,21 +239,21 @@ msgstr ""
msgid "Subject-alt name"
msgstr ""
#: authentik/crypto/models.py:34
#: authentik/crypto/models.py:35
msgid "PEM-encoded Certificate data"
msgstr ""
#: authentik/crypto/models.py:37
#: authentik/crypto/models.py:38
msgid ""
"Optional Private Key. If this is set, you can use this keypair for "
"encryption."
msgstr ""
#: authentik/crypto/models.py:100
#: authentik/crypto/models.py:107
msgid "Certificate-Key Pair"
msgstr ""
#: authentik/crypto/models.py:101
#: authentik/crypto/models.py:108
msgid "Certificate-Key Pairs"
msgstr ""
@ -250,89 +262,89 @@ msgstr ""
msgid "Successfully imported %(count)d files."
msgstr ""
#: authentik/events/models.py:288
#: authentik/events/models.py:294
msgid "Event"
msgstr ""
#: authentik/events/models.py:289
#: authentik/events/models.py:295
msgid "Events"
msgstr ""
#: authentik/events/models.py:295
#: authentik/events/models.py:301
msgid "authentik inbuilt notifications"
msgstr ""
#: authentik/events/models.py:296
#: authentik/events/models.py:302
msgid "Generic Webhook"
msgstr ""
#: authentik/events/models.py:297
#: authentik/events/models.py:303
msgid "Slack Webhook (Slack/Discord)"
msgstr ""
#: authentik/events/models.py:298
#: authentik/events/models.py:304
msgid "Email"
msgstr ""
#: authentik/events/models.py:316
#: authentik/events/models.py:322
msgid ""
"Only send notification once, for example when sending a webhook into a chat "
"channel."
msgstr ""
#: authentik/events/models.py:374
#: authentik/events/models.py:380
msgid "Severity"
msgstr ""
#: authentik/events/models.py:379
#: authentik/events/models.py:385
msgid "Dispatched for user"
msgstr ""
#: authentik/events/models.py:456
#: authentik/events/models.py:468
msgid "Notification Transport"
msgstr ""
#: authentik/events/models.py:457
#: authentik/events/models.py:469
msgid "Notification Transports"
msgstr ""
#: authentik/events/models.py:463
#: authentik/events/models.py:475
msgid "Notice"
msgstr ""
#: authentik/events/models.py:464
#: authentik/events/models.py:476
msgid "Warning"
msgstr ""
#: authentik/events/models.py:465
#: authentik/events/models.py:477
msgid "Alert"
msgstr ""
#: authentik/events/models.py:485
#: authentik/events/models.py:503
msgid "Notification"
msgstr ""
#: authentik/events/models.py:486
#: authentik/events/models.py:504
msgid "Notifications"
msgstr ""
#: authentik/events/models.py:506
#: authentik/events/models.py:524
msgid "Controls which severity level the created notifications will have."
msgstr ""
#: authentik/events/models.py:526
#: authentik/events/models.py:550
msgid "Notification Rule"
msgstr ""
#: authentik/events/models.py:527
#: authentik/events/models.py:551
msgid "Notification Rules"
msgstr ""
#: authentik/events/models.py:548
#: authentik/events/models.py:572
msgid "Notification Webhook Mapping"
msgstr ""
#: authentik/events/models.py:549
#: authentik/events/models.py:573
msgid "Notification Webhook Mappings"
msgstr ""
@ -430,10 +442,6 @@ msgstr ""
msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'."
msgstr ""
#: authentik/managed/models.py:12
msgid "Managed by authentik"
msgstr ""
#: authentik/outposts/api/service_connections.py:132
msgid ""
"You can only use an empty kubeconfig when connecting to a local cluster."
@ -443,33 +451,33 @@ msgstr ""
msgid "Invalid kubeconfig"
msgstr ""
#: authentik/outposts/models.py:154
#: authentik/outposts/models.py:155
msgid "Outpost Service-Connection"
msgstr ""
#: authentik/outposts/models.py:155
#: authentik/outposts/models.py:156
msgid "Outpost Service-Connections"
msgstr ""
#: authentik/outposts/models.py:191
#: authentik/outposts/models.py:192
msgid ""
"Certificate/Key used for authentication. Can be left empty for no "
"authentication."
msgstr ""
#: authentik/outposts/models.py:204
#: authentik/outposts/models.py:211
msgid "Docker Service-Connection"
msgstr ""
#: authentik/outposts/models.py:205
#: authentik/outposts/models.py:212
msgid "Docker Service-Connections"
msgstr ""
#: authentik/outposts/models.py:230
#: authentik/outposts/models.py:243
msgid "Kubernetes Service-Connection"
msgstr ""
#: authentik/outposts/models.py:231
#: authentik/outposts/models.py:244
msgid "Kubernetes Service-Connections"
msgstr ""
@ -666,184 +674,184 @@ msgstr ""
msgid "LDAP Providers"
msgstr ""
#: authentik/providers/oauth2/models.py:37
#: authentik/providers/oauth2/models.py:38
msgid "Confidential"
msgstr ""
#: authentik/providers/oauth2/models.py:38
#: authentik/providers/oauth2/models.py:39
msgid "Public"
msgstr ""
#: authentik/providers/oauth2/models.py:60
#: authentik/providers/oauth2/models.py:61
msgid "Based on the Hashed User ID"
msgstr ""
#: authentik/providers/oauth2/models.py:61
#: authentik/providers/oauth2/models.py:62
msgid "Based on the username"
msgstr ""
#: authentik/providers/oauth2/models.py:64
#: authentik/providers/oauth2/models.py:65
msgid "Based on the User's Email. This is recommended over the UPN method."
msgstr ""
#: authentik/providers/oauth2/models.py:80
#: authentik/providers/oauth2/models.py:81
msgid "Same identifier is used for all providers"
msgstr ""
#: authentik/providers/oauth2/models.py:82
#: authentik/providers/oauth2/models.py:83
msgid "Each provider has a different issuer, based on the application slug."
msgstr ""
#: authentik/providers/oauth2/models.py:89
#: authentik/providers/oauth2/models.py:90
msgid "code (Authorization Code Flow)"
msgstr ""
#: authentik/providers/oauth2/models.py:90
#: authentik/providers/oauth2/models.py:91
msgid "id_token (Implicit Flow)"
msgstr ""
#: authentik/providers/oauth2/models.py:91
#: authentik/providers/oauth2/models.py:92
msgid "id_token token (Implicit Flow)"
msgstr ""
#: authentik/providers/oauth2/models.py:92
#: authentik/providers/oauth2/models.py:93
msgid "code token (Hybrid Flow)"
msgstr ""
#: authentik/providers/oauth2/models.py:93
#: authentik/providers/oauth2/models.py:94
msgid "code id_token (Hybrid Flow)"
msgstr ""
#: authentik/providers/oauth2/models.py:94
#: authentik/providers/oauth2/models.py:95
msgid "code id_token token (Hybrid Flow)"
msgstr ""
#: authentik/providers/oauth2/models.py:100
#: authentik/providers/oauth2/models.py:101
msgid "HS256 (Symmetric Encryption)"
msgstr ""
#: authentik/providers/oauth2/models.py:101
#: authentik/providers/oauth2/models.py:102
msgid "RS256 (Asymmetric Encryption)"
msgstr ""
#: authentik/providers/oauth2/models.py:102
#: authentik/providers/oauth2/models.py:103
msgid "ES256 (Asymmetric Encryption)"
msgstr ""
#: authentik/providers/oauth2/models.py:108
#: authentik/providers/oauth2/models.py:109
msgid "Scope used by the client"
msgstr ""
#: authentik/providers/oauth2/models.py:134
#: authentik/providers/oauth2/models.py:135
msgid "Scope Mapping"
msgstr ""
#: authentik/providers/oauth2/models.py:135
#: authentik/providers/oauth2/models.py:136
msgid "Scope Mappings"
msgstr ""
#: authentik/providers/oauth2/models.py:145
#: authentik/providers/oauth2/models.py:146
msgid "Client Type"
msgstr ""
#: authentik/providers/oauth2/models.py:147
#: authentik/providers/oauth2/models.py:148
msgid ""
"Confidential clients are capable of maintaining the confidentiality of their "
"credentials. Public clients are incapable"
msgstr ""
#: authentik/providers/oauth2/models.py:154
#: authentik/providers/oauth2/models.py:155
msgid "Client ID"
msgstr ""
#: authentik/providers/oauth2/models.py:160
#: authentik/providers/oauth2/models.py:161
msgid "Client Secret"
msgstr ""
#: authentik/providers/oauth2/models.py:166
#: authentik/providers/oauth2/models.py:167
msgid "Redirect URIs"
msgstr ""
#: authentik/providers/oauth2/models.py:167
#: authentik/providers/oauth2/models.py:168
msgid "Enter each URI on a new line."
msgstr ""
#: authentik/providers/oauth2/models.py:172
#: authentik/providers/oauth2/models.py:173
msgid "Include claims in id_token"
msgstr ""
#: authentik/providers/oauth2/models.py:220
#: authentik/providers/oauth2/models.py:221
msgid "Signing Key"
msgstr ""
#: authentik/providers/oauth2/models.py:224
#: authentik/providers/oauth2/models.py:225
msgid ""
"Key used to sign the tokens. Only required when JWT Algorithm is set to "
"RS256."
msgstr ""
#: authentik/providers/oauth2/models.py:231
#: authentik/providers/oauth2/models.py:232
msgid ""
"Any JWT signed by the JWK of the selected source can be used to authenticate."
msgstr ""
#: authentik/providers/oauth2/models.py:313
#: authentik/providers/oauth2/models.py:314
msgid "OAuth2/OpenID Provider"
msgstr ""
#: authentik/providers/oauth2/models.py:314
#: authentik/providers/oauth2/models.py:315
msgid "OAuth2/OpenID Providers"
msgstr ""
#: authentik/providers/oauth2/models.py:322
#: authentik/providers/oauth2/models.py:323
msgid "Scopes"
msgstr ""
#: authentik/providers/oauth2/models.py:341
#: authentik/providers/oauth2/models.py:342
msgid "Code"
msgstr ""
#: authentik/providers/oauth2/models.py:342
#: authentik/providers/oauth2/models.py:343
msgid "Nonce"
msgstr ""
#: authentik/providers/oauth2/models.py:343
#: authentik/providers/oauth2/models.py:344
msgid "Is Authentication?"
msgstr ""
#: authentik/providers/oauth2/models.py:344
#: authentik/providers/oauth2/models.py:345
msgid "Code Challenge"
msgstr ""
#: authentik/providers/oauth2/models.py:346
#: authentik/providers/oauth2/models.py:347
msgid "Code Challenge Method"
msgstr ""
#: authentik/providers/oauth2/models.py:360
#: authentik/providers/oauth2/models.py:367
msgid "Authorization Code"
msgstr ""
#: authentik/providers/oauth2/models.py:361
#: authentik/providers/oauth2/models.py:368
msgid "Authorization Codes"
msgstr ""
#: authentik/providers/oauth2/models.py:404
#: authentik/providers/oauth2/models.py:411
msgid "Access Token"
msgstr ""
#: authentik/providers/oauth2/models.py:405
#: authentik/providers/oauth2/models.py:412
msgid "Refresh Token"
msgstr ""
#: authentik/providers/oauth2/models.py:406
#: authentik/providers/oauth2/models.py:413
msgid "ID Token"
msgstr ""
#: authentik/providers/oauth2/models.py:409
#: authentik/providers/oauth2/models.py:422
msgid "OAuth2 Token"
msgstr ""
#: authentik/providers/oauth2/models.py:410
#: authentik/providers/oauth2/models.py:423
msgid "OAuth2 Tokens"
msgstr ""
@ -870,42 +878,42 @@ msgstr ""
msgid "authentik API Access on behalf of your user"
msgstr ""
#: authentik/providers/proxy/models.py:47
#: authentik/providers/proxy/models.py:54
msgid "Validate SSL Certificates of upstream servers"
msgstr ""
#: authentik/providers/proxy/models.py:48
#: authentik/providers/proxy/models.py:55
msgid "Internal host SSL Validation"
msgstr ""
#: authentik/providers/proxy/models.py:54
#: authentik/providers/proxy/models.py:61
msgid ""
"Enable support for forwardAuth in traefik and nginx auth_request. Exclusive "
"with internal_host."
msgstr ""
#: authentik/providers/proxy/models.py:72
#: authentik/providers/proxy/models.py:79
msgid "Set HTTP-Basic Authentication"
msgstr ""
#: authentik/providers/proxy/models.py:74
#: authentik/providers/proxy/models.py:81
msgid ""
"Set a custom HTTP-Basic Authentication header based on values from authentik."
msgstr ""
#: authentik/providers/proxy/models.py:79
#: authentik/providers/proxy/models.py:86
msgid "HTTP-Basic Username Key"
msgstr ""
#: authentik/providers/proxy/models.py:89
#: authentik/providers/proxy/models.py:96
msgid "HTTP-Basic Password Key"
msgstr ""
#: authentik/providers/proxy/models.py:144
#: authentik/providers/proxy/models.py:151
msgid "Proxy Provider"
msgstr ""
#: authentik/providers/proxy/models.py:145
#: authentik/providers/proxy/models.py:152
msgid "Proxy Providers"
msgstr ""
@ -1213,11 +1221,11 @@ msgstr ""
msgid "Okta OAuth Sources"
msgstr ""
#: authentik/sources/oauth/models.py:220
#: authentik/sources/oauth/models.py:228
msgid "User OAuth Source Connection"
msgstr ""
#: authentik/sources/oauth/models.py:221
#: authentik/sources/oauth/models.py:229
msgid "User OAuth Source Connections"
msgstr ""
@ -1245,11 +1253,11 @@ msgstr ""
msgid "Plex Sources"
msgstr ""
#: authentik/sources/plex/models.py:104
#: authentik/sources/plex/models.py:110
msgid "User Plex Source Connection"
msgstr ""
#: authentik/sources/plex/models.py:105
#: authentik/sources/plex/models.py:111
msgid "User Plex Source Connections"
msgstr ""
@ -1322,42 +1330,42 @@ msgstr ""
msgid "SAML Sources"
msgstr ""
#: authentik/stages/authenticator_duo/models.py:64
#: authentik/stages/authenticator_duo/models.py:65
msgid "Duo Authenticator Setup Stage"
msgstr ""
#: authentik/stages/authenticator_duo/models.py:65
#: authentik/stages/authenticator_duo/models.py:66
msgid "Duo Authenticator Setup Stages"
msgstr ""
#: authentik/stages/authenticator_duo/models.py:84
#: authentik/stages/authenticator_duo/models.py:90
msgid "Duo Device"
msgstr ""
#: authentik/stages/authenticator_duo/models.py:85
#: authentik/stages/authenticator_duo/models.py:91
msgid "Duo Devices"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:53
#: authentik/stages/authenticator_sms/models.py:56
msgid ""
"When enabled, the Phone number is only used during enrollment to verify the "
"users authenticity. Only a hash of the phone number is saved to ensure it is "
"not re-used in the future."
msgstr ""
#: authentik/stages/authenticator_sms/models.py:167
#: authentik/stages/authenticator_sms/models.py:158
msgid "SMS Authenticator Setup Stage"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:168
#: authentik/stages/authenticator_sms/models.py:159
msgid "SMS Authenticator Setup Stages"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:207
#: authentik/stages/authenticator_sms/models.py:204
msgid "SMS Device"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:208
#: authentik/stages/authenticator_sms/models.py:205
msgid "SMS Devices"
msgstr ""
@ -1431,19 +1439,19 @@ msgstr ""
msgid "Authenticator Validation Stages"
msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:112
#: authentik/stages/authenticator_webauthn/models.py:113
msgid "WebAuthn Authenticator Setup Stage"
msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:113
#: authentik/stages/authenticator_webauthn/models.py:114
msgid "WebAuthn Authenticator Setup Stages"
msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:146
#: authentik/stages/authenticator_webauthn/models.py:153
msgid "WebAuthn Device"
msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:147
#: authentik/stages/authenticator_webauthn/models.py:154
msgid "WebAuthn Devices"
msgstr ""
@ -1465,19 +1473,19 @@ msgstr ""
msgid "Captcha Stages"
msgstr ""
#: authentik/stages/consent/models.py:50
#: authentik/stages/consent/models.py:51
msgid "Consent Stage"
msgstr ""
#: authentik/stages/consent/models.py:51
#: authentik/stages/consent/models.py:52
msgid "Consent Stages"
msgstr ""
#: authentik/stages/consent/models.py:67
#: authentik/stages/consent/models.py:74
msgid "User Consent"
msgstr ""
#: authentik/stages/consent/models.py:68
#: authentik/stages/consent/models.py:75
msgid "User Consents"
msgstr ""
@ -1645,27 +1653,27 @@ msgstr ""
msgid "Log in"
msgstr ""
#: authentik/stages/invitation/models.py:46
#: authentik/stages/invitation/models.py:47
msgid "Invitation Stage"
msgstr ""
#: authentik/stages/invitation/models.py:47
#: authentik/stages/invitation/models.py:48
msgid "Invitation Stages"
msgstr ""
#: authentik/stages/invitation/models.py:59
#: authentik/stages/invitation/models.py:60
msgid "When enabled, the invitation will be deleted after usage."
msgstr ""
#: authentik/stages/invitation/models.py:66
#: authentik/stages/invitation/models.py:67
msgid "Optional fixed data to enforce on user enrollment."
msgstr ""
#: authentik/stages/invitation/models.py:74
#: authentik/stages/invitation/models.py:81
msgid "Invitation"
msgstr ""
#: authentik/stages/invitation/models.py:75
#: authentik/stages/invitation/models.py:82
msgid "Invitations"
msgstr ""
@ -1817,16 +1825,16 @@ msgstr ""
msgid "No Pending data."
msgstr ""
#: authentik/tenants/models.py:18
#: authentik/tenants/models.py:20
msgid ""
"Domain that activates this tenant. Can be a superset, i.e. `a.b` for `aa.b` "
"and `ba.b`"
msgstr ""
#: authentik/tenants/models.py:80
#: authentik/tenants/models.py:87
msgid "Tenant"
msgstr ""
#: authentik/tenants/models.py:81
#: authentik/tenants/models.py:88
msgid "Tenants"
msgstr ""

View file

@ -5869,7 +5869,7 @@ paths:
/flows/instances/{slug}/export/:
get:
operationId: flows_instances_export_retrieve
description: Export flow to .akflow file
description: Export flow to .yaml file
parameters:
- in: path
name: slug
@ -6013,7 +6013,7 @@ paths:
/flows/instances/import_flow/:
post:
operationId: flows_instances_import_flow_create
description: Import flow from .akflow file
description: Import flow from .yaml file
tags:
- flows
requestBody:
@ -6030,6 +6030,215 @@ paths:
description: Bad request
'403':
$ref: '#/components/schemas/GenericError'
/managed/blueprints/:
get:
operationId: managed_blueprints_list
description: Blueprint instances
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: query
name: path
schema:
type: string
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- managed
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedBlueprintInstanceList'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
post:
operationId: managed_blueprints_create
description: Blueprint instances
tags:
- managed
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintInstanceRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintInstance'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/managed/blueprints/{instance_uuid}/:
get:
operationId: managed_blueprints_retrieve
description: Blueprint instances
parameters:
- in: path
name: instance_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Blueprint Instance.
required: true
tags:
- managed
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintInstance'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
put:
operationId: managed_blueprints_update
description: Blueprint instances
parameters:
- in: path
name: instance_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Blueprint Instance.
required: true
tags:
- managed
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintInstanceRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintInstance'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
patch:
operationId: managed_blueprints_partial_update
description: Blueprint instances
parameters:
- in: path
name: instance_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Blueprint Instance.
required: true
tags:
- managed
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedBlueprintInstanceRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/BlueprintInstance'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
delete:
operationId: managed_blueprints_destroy
description: Blueprint instances
parameters:
- in: path
name: instance_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Blueprint Instance.
required: true
tags:
- managed
security:
- authentik: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/managed/blueprints/available/:
get:
operationId: managed_blueprints_available_list
description: Get blueprints
tags:
- managed
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
type: string
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/oauth2/authorization_codes/:
get:
operationId: oauth2_authorization_codes_list
@ -8145,12 +8354,12 @@ paths:
enum:
- authentik.admin
- authentik.api
- authentik.blueprints
- authentik.core
- authentik.crypto
- authentik.events
- authentik.flows
- authentik.lib
- authentik.managed
- authentik.outposts
- authentik.policies
- authentik.policies.dummy
@ -19607,7 +19816,7 @@ components:
- authentik.stages.user_logout
- authentik.stages.user_write
- authentik.tenants
- authentik.managed
- authentik.blueprints
- authentik.core
type: string
AppleChallengeResponseRequest:
@ -20653,6 +20862,60 @@ components:
- POST
- POST_AUTO
type: string
BlueprintInstance:
type: object
description: Info about a single blueprint instance file
properties:
name:
type: string
path:
type: string
context:
type: object
additionalProperties: {}
last_applied:
type: string
format: date-time
readOnly: true
status:
$ref: '#/components/schemas/BlueprintInstanceStatusEnum'
enabled:
type: boolean
required:
- context
- last_applied
- name
- path
- status
BlueprintInstanceRequest:
type: object
description: Info about a single blueprint instance file
properties:
name:
type: string
minLength: 1
path:
type: string
minLength: 1
context:
type: object
additionalProperties: {}
status:
$ref: '#/components/schemas/BlueprintInstanceStatusEnum'
enabled:
type: boolean
required:
- context
- name
- path
- status
BlueprintInstanceStatusEnum:
enum:
- successful
- warning
- error
- unknown
type: string
Cache:
type: object
description: Generic cache stats for an object
@ -24565,6 +24828,41 @@ components:
required:
- pagination
- results
PaginatedBlueprintInstanceList:
type: object
properties:
pagination:
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
results:
type: array
items:
$ref: '#/components/schemas/BlueprintInstance'
required:
- pagination
- results
PaginatedCaptchaStageList:
type: object
properties:
@ -27472,6 +27770,23 @@ components:
minLength: 1
description: If any of the user's device has been used within this threshold,
this stage will be skipped
PatchedBlueprintInstanceRequest:
type: object
description: Info about a single blueprint instance file
properties:
name:
type: string
minLength: 1
path:
type: string
minLength: 1
context:
type: object
additionalProperties: {}
status:
$ref: '#/components/schemas/BlueprintInstanceStatusEnum'
enabled:
type: boolean
PatchedCaptchaStageRequest:
type: object
description: CaptchaStage Serializer
@ -31291,13 +31606,6 @@ components:
maxLength: 16
required:
- token
StatusEnum:
enum:
- SUCCESSFUL
- WARNING
- ERROR
- UNKNOWN
type: string
SubModeEnum:
enum:
- hashed_user_id
@ -31406,7 +31714,7 @@ components:
type: string
format: date-time
status:
$ref: '#/components/schemas/StatusEnum'
$ref: '#/components/schemas/TaskStatusEnum'
messages:
type: array
items: {}
@ -31416,6 +31724,13 @@ components:
- task_description
- task_finish_timestamp
- task_name
TaskStatusEnum:
enum:
- SUCCESSFUL
- WARNING
- ERROR
- UNKNOWN
type: string
Tenant:
type: object
description: Tenant Serializer

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