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:
parent
882250a85e
commit
89c84f10d0
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
@ -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",
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
56
authentik/blueprints/api.py
Normal file
56
authentik/blueprints/api.py
Normal 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)
|
15
authentik/blueprints/apps.py
Normal file
15
authentik/blueprints/apps.py
Normal 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()
|
22
authentik/blueprints/management/commands/apply_blueprint.py
Normal file
22
authentik/blueprints/management/commands/apply_blueprint.py
Normal 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)
|
|
@ -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()
|
||||
|
66
authentik/blueprints/migrations/0001_initial.py
Normal file
66
authentik/blueprints/migrations/0001_initial.py
Normal 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")},
|
||||
},
|
||||
),
|
||||
]
|
0
authentik/blueprints/migrations/__init__.py
Normal file
0
authentik/blueprints/migrations/__init__.py
Normal file
76
authentik/blueprints/models.py
Normal file
76
authentik/blueprints/models.py
Normal 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",
|
||||
),
|
||||
)
|
17
authentik/blueprints/settings.py
Normal file
17
authentik/blueprints/settings.py
Normal 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"},
|
||||
},
|
||||
}
|
|
@ -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()
|
0
authentik/blueprints/tests/__init__.py
Normal file
0
authentik/blueprints/tests/__init__.py
Normal 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):
|
34
authentik/blueprints/tests/test_models.py
Normal file
34
authentik/blueprints/tests/test_models.py
Normal 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))
|
|
@ -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())
|
|
@ -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))
|
0
authentik/blueprints/v1/__init__.py
Normal file
0
authentik/blueprints/v1/__init__.py
Normal file
177
authentik/blueprints/v1/common.py
Normal file
177
authentik/blueprints/v1/common.py
Normal 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"""
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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()})
|
||||
|
|
|
@ -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`"""
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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"""
|
|
@ -62,6 +62,7 @@ ldap:
|
|||
tls:
|
||||
ciphers: null
|
||||
|
||||
config_file_dir: "/config"
|
||||
cookie_domain: null
|
||||
disable_update_check: false
|
||||
disable_startup_analytics: false
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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
|
|
@ -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"},
|
||||
},
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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="",
|
||||
|
|
|
@ -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 = """
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = """
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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"""
|
||||
|
|
76
blueprints/default/0-flow-password-change.yaml
Normal file
76
blueprints/default/0-flow-password-change.yaml
Normal 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
|
102
blueprints/default/10-flow-default-authentication-flow.yaml
Normal file
102
blueprints/default/10-flow-default-authentication-flow.yaml
Normal 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
|
30
blueprints/default/10-flow-default-invalidation-flow.yaml
Normal file
30
blueprints/default/10-flow-default-invalidation-flow.yaml
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
121
blueprints/default/20-flow-default-source-enrollment.yaml
Normal file
121
blueprints/default/20-flow-default-source-enrollment.yaml
Normal 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
|
|
@ -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
|
157
blueprints/default/30-flow-default-user-settings-flow.yaml
Normal file
157
blueprints/default/30-flow-default-user-settings-flow.yaml
Normal 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
|
9
blueprints/default/90-default-tenant.yaml
Normal file
9
blueprints/default/90-default-tenant.yaml
Normal 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
|
|
@ -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:
|
|
@ -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:
|
|
@ -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:
|
|
@ -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:
|
|
@ -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:
|
|
@ -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:
|
|
@ -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 ""
|
||||
|
|
339
schema.yml
339
schema.yml
|
@ -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
Reference in a new issue