blueprints/cleanup (#3369)
This commit is contained in:
parent
2a5a975d9a
commit
ec42d378ab
2
.github/workflows/release-tag.yml
vendored
2
.github/workflows/release-tag.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
|||
echo "AUTHENTIK_TAG=latest" >> .env
|
||||
docker-compose up --no-start
|
||||
docker-compose start postgresql redis
|
||||
docker-compose run -u root server test
|
||||
docker-compose run -u root server test-all
|
||||
- name: Extract version number
|
||||
id: get_version
|
||||
uses: actions/github-script@v6
|
||||
|
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -27,5 +27,8 @@
|
|||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||
"typescript.tsdk": "./web/node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"yaml.schemas": {
|
||||
"./blueprints/schema.json": "blueprints/**/*.yaml"
|
||||
}
|
||||
}
|
||||
|
|
7
Makefile
7
Makefile
|
@ -52,10 +52,11 @@ lint:
|
|||
i18n-extract: i18n-extract-core web-extract
|
||||
|
||||
i18n-extract-core:
|
||||
./manage.py makemessages --ignore web --ignore internal --ignore web --ignore web-api --ignore website -l en
|
||||
ak makemessages --ignore web --ignore internal --ignore web --ignore web-api --ignore website -l en
|
||||
|
||||
gen-build:
|
||||
AUTHENTIK_DEBUG=true ./manage.py spectacular --file schema.yml
|
||||
AUTHENTIK_DEBUG=true ak make_blueprint_schema > blueprints/schema.json
|
||||
AUTHENTIK_DEBUG=true ak spectacular --file schema.yml
|
||||
|
||||
gen-clean:
|
||||
rm -rf web/api/src/
|
||||
|
@ -168,7 +169,7 @@ ci-pyright: ci--meta-debug
|
|||
pyright e2e lifecycle
|
||||
|
||||
ci-pending-migrations: ci--meta-debug
|
||||
./manage.py makemigrations --check
|
||||
ak makemigrations --check
|
||||
|
||||
install: web-install website-install
|
||||
poetry install
|
||||
|
|
|
@ -15,6 +15,7 @@ from authentik.blueprints.models import BlueprintInstance
|
|||
from authentik.blueprints.v1.tasks import BlueprintFile, apply_blueprint, blueprints_find
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.events.utils import sanitize_dict
|
||||
|
||||
|
||||
class ManagedSerializer:
|
||||
|
@ -85,7 +86,7 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
|
|||
def available(self, request: Request) -> Response:
|
||||
"""Get blueprints"""
|
||||
files: list[BlueprintFile] = blueprints_find.delay().get()
|
||||
return Response([asdict(file) for file in files])
|
||||
return Response([sanitize_dict(asdict(file)) for file in files])
|
||||
|
||||
@permission_required("authentik_blueprints.view_blueprintinstance")
|
||||
@extend_schema(
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
"""Generate JSON Schema for blueprints"""
|
||||
from json import dumps, loads
|
||||
from pathlib import Path
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.management.base import BaseCommand, no_translations
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.v1.importer import is_model_allowed
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Generate JSON Schema for blueprints"""
|
||||
|
||||
schema: dict
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, **options):
|
||||
"""Generate JSON Schema for blueprints"""
|
||||
path = Path(__file__).parent.joinpath("./schema_template.json")
|
||||
with open(path, "r", encoding="utf-8") as _template_file:
|
||||
self.schema = loads(_template_file.read())
|
||||
self.set_model_allowed()
|
||||
self.stdout.write(dumps(self.schema, indent=4))
|
||||
|
||||
def set_model_allowed(self):
|
||||
"""Set model enum"""
|
||||
model_names = []
|
||||
for model in apps.get_models():
|
||||
if not is_model_allowed(model):
|
||||
continue
|
||||
model_names.append(f"{model._meta.app_label}.{model._meta.model_name}")
|
||||
self.schema["properties"]["entries"]["items"]["properties"]["model"]["enum"] = model_names
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "http://example.com/example.json",
|
||||
"type": "object",
|
||||
"title": "authentik Blueprint schema",
|
||||
"default": {},
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"$id": "#/properties/version",
|
||||
"type": "integer",
|
||||
"title": "Blueprint version",
|
||||
"default": 1
|
||||
},
|
||||
"metadata": {
|
||||
"$id": "#/properties/metadata",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$id": "#entry",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"identifiers"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Commonly available field, may not exist on all models"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"identifiers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pk": {
|
||||
"description": "Commonly available field, may not exist on all models",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ from django.db import migrations, models
|
|||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from yaml import load
|
||||
|
||||
from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_SYSTEM
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
|
@ -37,7 +38,7 @@ def check_blueprint_v1_file(BlueprintInstance: type["BlueprintInstance"], path:
|
|||
if not instance:
|
||||
instance = BlueprintInstance(
|
||||
name=meta.name if meta else str(rel_path),
|
||||
path=str(path),
|
||||
path=str(rel_path),
|
||||
context={},
|
||||
status=BlueprintInstanceStatus.UNKNOWN,
|
||||
enabled=True,
|
||||
|
@ -62,7 +63,7 @@ def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEdit
|
|||
if Flow.objects.using(db_alias).all().exists():
|
||||
blueprint.enabled = False
|
||||
# System blueprints are always enabled
|
||||
if "/system/" in blueprint.path:
|
||||
if blueprint.metadata.get("labels", {}).get(LABEL_AUTHENTIK_SYSTEM, "").lower() == "true":
|
||||
blueprint.enabled = True
|
||||
blueprint.save()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ 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.blueprints.v1.importer import is_model_allowed
|
||||
from authentik.lib.models import SerializerModel
|
||||
|
||||
|
||||
|
@ -29,6 +29,6 @@ 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:
|
||||
if not is_model_allowed(model):
|
||||
continue
|
||||
setattr(TestModels, f"test_{app.label}_{model.__name__}", serializer_tester_factory(model))
|
||||
|
|
45
authentik/blueprints/tests/test_v1_api.py
Normal file
45
authentik/blueprints/tests/test_v1_api.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
"""Test blueprints v1 api"""
|
||||
from json import loads
|
||||
from tempfile import NamedTemporaryFile, mkdtemp
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from yaml import dump
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
TMP = mkdtemp("authentik-blueprints")
|
||||
|
||||
|
||||
class TestBlueprintsV1API(APITestCase):
|
||||
"""Test Blueprints API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
@CONFIG.patch("blueprints_dir", TMP)
|
||||
def test_api_available(self):
|
||||
"""Test valid file"""
|
||||
with NamedTemporaryFile(mode="w+", suffix=".yaml", dir=TMP) as file:
|
||||
file.write(
|
||||
dump(
|
||||
{
|
||||
"version": 1,
|
||||
"entries": [],
|
||||
}
|
||||
)
|
||||
)
|
||||
file.flush()
|
||||
res = self.client.get(reverse("authentik_api:blueprintinstance-available"))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
response = loads(res.content.decode())
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(
|
||||
response[0]["hash"],
|
||||
(
|
||||
"e52bb445b03cd36057258dc9f0ce0fbed8278498ee1470e45315293e5f026d1bd1f9b352"
|
||||
"6871c0003f5c07be5c3316d9d4a08444bd8fed1b3f03294e51e44522"
|
||||
),
|
||||
)
|
|
@ -35,19 +35,29 @@ from authentik.lib.models import SerializerModel
|
|||
from authentik.outposts.models import OutpostServiceConnection
|
||||
from authentik.policies.models import Policy, PolicyBindingModel
|
||||
|
||||
EXCLUDED_MODELS = (
|
||||
# Base classes
|
||||
Provider,
|
||||
Source,
|
||||
PropertyMapping,
|
||||
UserSourceConnection,
|
||||
Stage,
|
||||
OutpostServiceConnection,
|
||||
Policy,
|
||||
PolicyBindingModel,
|
||||
# Classes that have other dependencies
|
||||
AuthenticatedSession,
|
||||
)
|
||||
|
||||
def is_model_allowed(model: type[Model]) -> bool:
|
||||
"""Check if model is allowed"""
|
||||
# pylint: disable=imported-auth-user
|
||||
from django.contrib.auth.models import Group as DjangoGroup
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
|
||||
excluded_models = (
|
||||
DjangoUser,
|
||||
DjangoGroup,
|
||||
# Base classes
|
||||
Provider,
|
||||
Source,
|
||||
PropertyMapping,
|
||||
UserSourceConnection,
|
||||
Stage,
|
||||
OutpostServiceConnection,
|
||||
Policy,
|
||||
PolicyBindingModel,
|
||||
# Classes that have other dependencies
|
||||
AuthenticatedSession,
|
||||
)
|
||||
return model not in excluded_models
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
@ -123,8 +133,10 @@ class Importer:
|
|||
model_app_label, model_name = entry.model.split(".")
|
||||
model: type[SerializerModel] = apps.get_model(model_app_label, model_name)
|
||||
# Don't use isinstance since we don't want to check for inheritance
|
||||
if model in EXCLUDED_MODELS:
|
||||
if not is_model_allowed(model):
|
||||
raise EntryInvalidError(f"Model {model} not allowed")
|
||||
if entry.identifiers == {}:
|
||||
raise EntryInvalidError("No identifiers")
|
||||
|
||||
# If we try to validate without referencing a possible instance
|
||||
# we'll get a duplicate error, hence we load the model here and return
|
||||
|
@ -148,6 +160,7 @@ class Importer:
|
|||
pk=model_instance.pk,
|
||||
)
|
||||
serializer_kwargs["instance"] = model_instance
|
||||
serializer_kwargs["partial"] = True
|
||||
else:
|
||||
self.logger.debug("initialise new instance", model=model, **updated_identifiers)
|
||||
model_instance = model()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"""v1 blueprints tasks"""
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from glob import glob
|
||||
from hashlib import sha512
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
@ -43,7 +42,8 @@ class BlueprintFile:
|
|||
def blueprints_find():
|
||||
"""Find blueprints and return valid ones"""
|
||||
blueprints = []
|
||||
for file in glob(f"{CONFIG.y('blueprints_dir')}/**/*.yaml", recursive=True):
|
||||
root = Path(CONFIG.y("blueprints_dir"))
|
||||
for file in root.glob("**/*.yaml"):
|
||||
path = Path(file)
|
||||
with open(path, "r", encoding="utf-8") as blueprint_file:
|
||||
try:
|
||||
|
@ -57,7 +57,7 @@ def blueprints_find():
|
|||
if version != 1:
|
||||
continue
|
||||
file_hash = sha512(path.read_bytes()).hexdigest()
|
||||
blueprint = BlueprintFile(str(path), version, file_hash, path.stat().st_mtime)
|
||||
blueprint = BlueprintFile(path.relative_to(root), version, file_hash, path.stat().st_mtime)
|
||||
blueprint.meta = from_dict(BlueprintMetadata, metadata) if metadata else None
|
||||
if (
|
||||
blueprint.meta
|
||||
|
@ -88,11 +88,10 @@ def blueprints_discover(self: MonitoredTask):
|
|||
|
||||
def check_blueprint_v1_file(blueprint: BlueprintFile):
|
||||
"""Check if blueprint should be imported"""
|
||||
rel_path = Path(blueprint.path).relative_to(Path(CONFIG.y("blueprints_dir")))
|
||||
instance: BlueprintInstance = BlueprintInstance.objects.filter(path=blueprint.path).first()
|
||||
if not instance:
|
||||
instance = BlueprintInstance(
|
||||
name=blueprint.meta.name if blueprint.meta else str(rel_path),
|
||||
name=blueprint.meta.name if blueprint.meta else str(blueprint.path),
|
||||
path=blueprint.path,
|
||||
context={},
|
||||
status=BlueprintInstanceStatus.UNKNOWN,
|
||||
|
@ -119,8 +118,9 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
|
|||
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
|
||||
if not instance or not instance.enabled:
|
||||
return
|
||||
file_hash = sha512(Path(instance.path).read_bytes()).hexdigest()
|
||||
with open(instance.path, "r", encoding="utf-8") as blueprint_file:
|
||||
full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(instance.path))
|
||||
file_hash = sha512(full_path.read_bytes()).hexdigest()
|
||||
with open(full_path, "r", encoding="utf-8") as blueprint_file:
|
||||
importer = Importer(blueprint_file.read())
|
||||
valid, logs = importer.validate()
|
||||
if not valid:
|
||||
|
|
|
@ -1,29 +1,5 @@
|
|||
# Generated by Django 4.0.4 on 2022-05-30 18:08
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.events.models import TransportMode
|
||||
|
||||
|
||||
def notify_local_transport(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
|
||||
local_transport, _ = NotificationTransport.objects.using(db_alias).update_or_create(
|
||||
name="default-local-transport",
|
||||
defaults={"mode": TransportMode.LOCAL},
|
||||
)
|
||||
|
||||
for trigger in NotificationRule.objects.using(db_alias).filter(
|
||||
name__in=[
|
||||
"default-notify-configuration-error",
|
||||
"default-notify-exception",
|
||||
"default-notify-update",
|
||||
]
|
||||
):
|
||||
trigger.transports.add(local_transport)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -46,5 +22,4 @@ class Migration(migrations.Migration):
|
|||
default="local",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(notify_local_transport),
|
||||
]
|
||||
|
|
|
@ -1,130 +1,6 @@
|
|||
# Generated by Django 3.1.4 on 2021-01-10 18:57
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.events.models import EventAction, NotificationSeverity, TransportMode
|
||||
|
||||
|
||||
def notify_configuration_error(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
admin_group = (
|
||||
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
|
||||
)
|
||||
|
||||
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-configuration-error",
|
||||
defaults={"action": EventAction.CONFIGURATION_ERROR},
|
||||
)
|
||||
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
|
||||
name="default-notify-configuration-error",
|
||||
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
|
||||
)
|
||||
trigger.transports.set(
|
||||
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
|
||||
)
|
||||
trigger.save()
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy,
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def notify_update(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
admin_group = (
|
||||
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
|
||||
)
|
||||
|
||||
policy, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-update",
|
||||
defaults={"action": EventAction.UPDATE_AVAILABLE},
|
||||
)
|
||||
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
|
||||
name="default-notify-update",
|
||||
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
|
||||
)
|
||||
trigger.transports.set(
|
||||
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
|
||||
)
|
||||
trigger.save()
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy,
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def notify_exception(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Group = apps.get_model("authentik_core", "Group")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
EventMatcherPolicy = apps.get_model("authentik_policies_event_matcher", "EventMatcherPolicy")
|
||||
NotificationRule = apps.get_model("authentik_events", "NotificationRule")
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
admin_group = (
|
||||
Group.objects.using(db_alias).filter(name="authentik Admins", is_superuser=True).first()
|
||||
)
|
||||
|
||||
policy_policy_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-policy-exception",
|
||||
defaults={"action": EventAction.POLICY_EXCEPTION},
|
||||
)
|
||||
policy_pm_exc, _ = EventMatcherPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-match-property-mapping-exception",
|
||||
defaults={"action": EventAction.PROPERTY_MAPPING_EXCEPTION},
|
||||
)
|
||||
trigger, _ = NotificationRule.objects.using(db_alias).update_or_create(
|
||||
name="default-notify-exception",
|
||||
defaults={"group": admin_group, "severity": NotificationSeverity.ALERT},
|
||||
)
|
||||
trigger.transports.set(
|
||||
NotificationTransport.objects.using(db_alias).filter(name="default-email-transport")
|
||||
)
|
||||
trigger.save()
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy_policy_exc,
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
target=trigger,
|
||||
policy=policy_pm_exc,
|
||||
defaults={
|
||||
"order": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def transport_email_global(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
NotificationTransport = apps.get_model("authentik_events", "NotificationTransport")
|
||||
|
||||
NotificationTransport.objects.using(db_alias).update_or_create(
|
||||
name="default-email-transport",
|
||||
defaults={"mode": TransportMode.EMAIL},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -134,14 +10,6 @@ class Migration(migrations.Migration):
|
|||
"authentik_events",
|
||||
"0010_notification_notificationtransport_notificationrule",
|
||||
),
|
||||
("authentik_core", "0016_auto_20201202_2234"),
|
||||
("authentik_policies_event_matcher", "0003_auto_20210110_1907"),
|
||||
("authentik_policies", "0004_policy_execution_logging"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(transport_email_global),
|
||||
migrations.RunPython(notify_configuration_error),
|
||||
migrations.RunPython(notify_update),
|
||||
migrations.RunPython(notify_exception),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""event utilities"""
|
||||
import re
|
||||
from dataclasses import asdict, is_dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from uuid import UUID
|
||||
|
||||
|
@ -97,6 +98,8 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
|
|||
continue
|
||||
elif isinstance(value, City):
|
||||
final_dict[key] = GEOIP_READER.city_to_dict(value)
|
||||
elif isinstance(value, Path):
|
||||
final_dict[key] = str(value)
|
||||
elif isinstance(value, type):
|
||||
final_dict[key] = {
|
||||
"type": value.__name__,
|
||||
|
|
|
@ -1,103 +1,12 @@
|
|||
# Generated by Django 3.0.3 on 2020-05-08 14:30
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.stages.identification.models import UserFields
|
||||
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
|
||||
|
||||
|
||||
def create_default_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
PasswordStage = apps.get_model("authentik_stages_password", "PasswordStage")
|
||||
UserLoginStage = apps.get_model("authentik_stages_user_login", "UserLoginStage")
|
||||
IdentificationStage = apps.get_model("authentik_stages_identification", "IdentificationStage")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
identification_stage, _ = IdentificationStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authentication-identification",
|
||||
defaults={
|
||||
"user_fields": [UserFields.E_MAIL, UserFields.USERNAME],
|
||||
},
|
||||
)
|
||||
|
||||
password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authentication-password",
|
||||
defaults={"backends": [BACKEND_INBUILT, BACKEND_LDAP, BACKEND_APP_PASSWORD]},
|
||||
)
|
||||
|
||||
login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authentication-login"
|
||||
)
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-authentication-flow",
|
||||
designation=FlowDesignation.AUTHENTICATION,
|
||||
defaults={
|
||||
"name": "Welcome to authentik!",
|
||||
},
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=identification_stage,
|
||||
defaults={
|
||||
"order": 10,
|
||||
},
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=password_stage,
|
||||
defaults={
|
||||
"order": 20,
|
||||
},
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=login_stage,
|
||||
defaults={
|
||||
"order": 100,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def create_default_invalidation_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
UserLogoutStage = apps.get_model("authentik_stages_user_logout", "UserLogoutStage")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
UserLogoutStage.objects.using(db_alias).update_or_create(name="default-invalidation-logout")
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-invalidation-flow",
|
||||
designation=FlowDesignation.INVALIDATION,
|
||||
defaults={
|
||||
"name": "Logout",
|
||||
},
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=UserLogoutStage.objects.using(db_alias).first(),
|
||||
defaults={
|
||||
"order": 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0007_auto_20200703_2059"),
|
||||
("authentik_stages_user_login", "0001_initial"),
|
||||
("authentik_stages_user_logout", "0001_initial"),
|
||||
("authentik_stages_password", "0001_initial"),
|
||||
("authentik_stages_identification", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_authentication_flow),
|
||||
migrations.RunPython(create_default_invalidation_flow),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,151 +1,12 @@
|
|||
# Generated by Django 3.0.6 on 2020-05-23 15:47
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.stages.prompt.models import FieldTypes
|
||||
|
||||
FLOW_POLICY_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"""
|
||||
PROMPT_POLICY_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', {})"""
|
||||
|
||||
|
||||
def create_default_source_enrollment_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
|
||||
ExpressionPolicy = apps.get_model("authentik_policies_expression", "ExpressionPolicy")
|
||||
|
||||
PromptStage = apps.get_model("authentik_stages_prompt", "PromptStage")
|
||||
Prompt = apps.get_model("authentik_stages_prompt", "Prompt")
|
||||
UserWriteStage = apps.get_model("authentik_stages_user_write", "UserWriteStage")
|
||||
UserLoginStage = apps.get_model("authentik_stages_user_login", "UserLoginStage")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
# Create a policy that only allows this flow when doing an SSO Request
|
||||
flow_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-source-enrollment-if-sso",
|
||||
defaults={"expression": FLOW_POLICY_EXPRESSION},
|
||||
)
|
||||
|
||||
# This creates a Flow used by sources to enroll users
|
||||
# It makes sure that a username is set, and if not, prompts the user for a Username
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-source-enrollment",
|
||||
designation=FlowDesignation.ENROLLMENT,
|
||||
defaults={
|
||||
"name": "Welcome to authentik! Please select a username.",
|
||||
},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
policy=flow_policy, target=flow, defaults={"order": 0}
|
||||
)
|
||||
|
||||
# PromptStage to ask user for their username
|
||||
prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create(
|
||||
name="default-source-enrollment-prompt",
|
||||
)
|
||||
prompt, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="username",
|
||||
defaults={
|
||||
"label": "Username",
|
||||
"type": FieldTypes.TEXT,
|
||||
"required": True,
|
||||
"placeholder": "Username",
|
||||
"order": 100,
|
||||
},
|
||||
)
|
||||
prompt_stage.fields.add(prompt)
|
||||
|
||||
# Policy to only trigger prompt when no username is given
|
||||
prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-source-enrollment-if-username",
|
||||
defaults={"expression": PROMPT_POLICY_EXPRESSION},
|
||||
)
|
||||
|
||||
# UserWrite stage to create the user, and login stage to log user in
|
||||
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
|
||||
name="default-source-enrollment-write"
|
||||
)
|
||||
user_login, _ = UserLoginStage.objects.using(db_alias).update_or_create(
|
||||
name="default-source-enrollment-login"
|
||||
)
|
||||
|
||||
binding, _ = FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=prompt_stage,
|
||||
defaults={"order": 0, "re_evaluate_policies": True},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
policy=prompt_policy, target=binding, defaults={"order": 0}
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=user_write, defaults={"order": 1}
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=user_login, defaults={"order": 2}
|
||||
)
|
||||
|
||||
|
||||
def create_default_source_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
|
||||
ExpressionPolicy = apps.get_model("authentik_policies_expression", "ExpressionPolicy")
|
||||
|
||||
UserLoginStage = apps.get_model("authentik_stages_user_login", "UserLoginStage")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
# Create a policy that only allows this flow when doing an SSO Request
|
||||
flow_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-source-authentication-if-sso",
|
||||
defaults={
|
||||
"expression": FLOW_POLICY_EXPRESSION,
|
||||
},
|
||||
)
|
||||
|
||||
# This creates a Flow used by sources to authenticate users
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-source-authentication",
|
||||
designation=FlowDesignation.AUTHENTICATION,
|
||||
defaults={
|
||||
"name": "Welcome to authentik!",
|
||||
},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
policy=flow_policy, target=flow, defaults={"order": 0}
|
||||
)
|
||||
|
||||
user_login, _ = UserLoginStage.objects.using(db_alias).update_or_create(
|
||||
name="default-source-authentication-login"
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=user_login, defaults={"order": 0}
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0008_default_flows"),
|
||||
("authentik_policies", "0001_initial"),
|
||||
("authentik_policies_expression", "0001_initial"),
|
||||
("authentik_stages_prompt", "0001_initial"),
|
||||
("authentik_stages_user_write", "0001_initial"),
|
||||
("authentik_stages_user_login", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_source_enrollment_flow),
|
||||
migrations.RunPython(create_default_source_authentication_flow),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,46 +1,12 @@
|
|||
# Generated by Django 3.0.6 on 2020-05-24 11:34
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
|
||||
|
||||
def create_default_provider_authorization_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
ConsentStage = apps.get_model("authentik_stages_consent", "ConsentStage")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
# Empty flow for providers where consent is implicitly given
|
||||
Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-provider-authorization-implicit-consent",
|
||||
designation=FlowDesignation.AUTHORIZATION,
|
||||
defaults={"name": "Authorize Application"},
|
||||
)
|
||||
|
||||
# Flow with consent form to obtain explicit user consent
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-provider-authorization-explicit-consent",
|
||||
designation=FlowDesignation.AUTHORIZATION,
|
||||
defaults={"name": "Authorize Application"},
|
||||
)
|
||||
stage, _ = ConsentStage.objects.using(db_alias).update_or_create(
|
||||
name="default-provider-authorization-consent"
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=stage, defaults={"order": 0}
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0009_source_flows"),
|
||||
("authentik_stages_consent", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(create_default_provider_authorization_flow)]
|
||||
operations = []
|
||||
|
|
|
@ -1,27 +1,5 @@
|
|||
# Generated by Django 3.1 on 2020-08-28 13:14
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def add_title_for_defaults(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
slug_title_map = {
|
||||
"default-authentication-flow": "Welcome to authentik!",
|
||||
"default-invalidation-flow": "Default Invalidation Flow",
|
||||
"default-source-enrollment": "Welcome to authentik! Please select a username.",
|
||||
"default-source-authentication": "Welcome to authentik!",
|
||||
"default-provider-authorization-implicit-consent": "Redirecting to %(app)s",
|
||||
"default-provider-authorization-explicit-consent": "Redirecting to %(app)s",
|
||||
"default-password-change": "Change password",
|
||||
}
|
||||
db_alias = schema_editor.connection.alias
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
for flow in Flow.objects.using(db_alias).all():
|
||||
if flow.slug in slug_title_map:
|
||||
flow.title = slug_title_map[flow.slug]
|
||||
else:
|
||||
flow.title = flow.name
|
||||
flow.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -45,7 +23,6 @@ class Migration(migrations.Migration):
|
|||
field=models.TextField(default="", blank=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(add_title_for_defaults),
|
||||
migrations.AlterField(
|
||||
model_name="flow",
|
||||
name="title",
|
||||
|
|
|
@ -1,27 +1,6 @@
|
|||
# Generated by Django 3.1.1 on 2020-09-25 23:32
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
# First stage for default-source-enrollment flow (prompt stage)
|
||||
# needs to have its policy re-evaluated
|
||||
def update_default_source_enrollment_flow_binding(
|
||||
apps: Apps, schema_editor: BaseDatabaseSchemaEditor
|
||||
):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
flows = Flow.objects.using(db_alias).filter(slug="default-source-enrollment")
|
||||
if not flows.exists():
|
||||
return
|
||||
flow = flows.first()
|
||||
|
||||
binding = FlowStageBinding.objects.get(target=flow, order=0)
|
||||
binding.re_evaluate_policies = True
|
||||
binding.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -47,5 +26,4 @@ class Migration(migrations.Migration):
|
|||
help_text="When this option is enabled, the planner will re-evaluate policies bound to this binding.",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(update_default_source_enrollment_flow_binding),
|
||||
]
|
||||
|
|
|
@ -1,141 +1,12 @@
|
|||
# Generated by Django 3.1.7 on 2021-04-06 13:25
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.contrib.auth.hashers import is_password_usable
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
|
||||
PW_USABLE_POLICY_EXPRESSION = """# This policy ensures that the setup flow can only be
|
||||
# executed when the admin user doesn't have a password set
|
||||
akadmin = ak_user_by(username="akadmin")
|
||||
return not akadmin.has_usable_password()"""
|
||||
PREFILL_POLICY_EXPRESSION = """# This policy sets the user for the currently running flow
|
||||
# by injecting "pending_user"
|
||||
akadmin = ak_user_by(username="akadmin")
|
||||
context["flow_plan"].context["pending_user"] = akadmin
|
||||
return True"""
|
||||
|
||||
|
||||
def create_default_oobe_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.stages.prompt.models import FieldTypes
|
||||
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
|
||||
PolicyBinding = apps.get_model("authentik_policies", "PolicyBinding")
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
UserLoginStage = apps.get_model("authentik_stages_user_login", "UserLoginStage")
|
||||
UserWriteStage = apps.get_model("authentik_stages_user_write", "UserWriteStage")
|
||||
PromptStage = apps.get_model("authentik_stages_prompt", "PromptStage")
|
||||
Prompt = apps.get_model("authentik_stages_prompt", "Prompt")
|
||||
|
||||
ExpressionPolicy = apps.get_model("authentik_policies_expression", "ExpressionPolicy")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
# Only create the flow if the akadmin user exists,
|
||||
# and has an un-usable password
|
||||
akadmins = User.objects.filter(username="akadmin")
|
||||
if not akadmins.exists():
|
||||
return
|
||||
akadmin = akadmins.first()
|
||||
if is_password_usable(akadmin.password):
|
||||
return
|
||||
|
||||
# Create a policy that sets the flow's user
|
||||
prefill_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-oobe-prefill-user",
|
||||
defaults={"expression": PREFILL_POLICY_EXPRESSION},
|
||||
)
|
||||
password_usable_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-oobe-password-usable",
|
||||
defaults={"expression": PW_USABLE_POLICY_EXPRESSION},
|
||||
)
|
||||
|
||||
prompt_header, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="oobe-header-text",
|
||||
defaults={
|
||||
"label": "oobe-header-text",
|
||||
"type": FieldTypes.STATIC,
|
||||
"placeholder": "Welcome to authentik! Please set a password for the default admin user, akadmin.",
|
||||
"order": 100,
|
||||
},
|
||||
)
|
||||
prompt_email, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="email",
|
||||
defaults={
|
||||
"label": "Email",
|
||||
"type": FieldTypes.EMAIL,
|
||||
"placeholder": "Admin email",
|
||||
"order": 101,
|
||||
},
|
||||
)
|
||||
password_first = Prompt.objects.using(db_alias).get(field_key="password")
|
||||
password_second = Prompt.objects.using(db_alias).get(field_key="password_repeat")
|
||||
|
||||
prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create(
|
||||
name="default-oobe-password",
|
||||
)
|
||||
prompt_stage.fields.set([prompt_header, prompt_email, password_first, password_second])
|
||||
prompt_stage.save()
|
||||
|
||||
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
|
||||
name="default-password-change-write"
|
||||
)
|
||||
login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authentication-login"
|
||||
)
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="initial-setup",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={
|
||||
"name": "default-oobe-setup",
|
||||
"title": "Welcome to authentik!",
|
||||
},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
policy=password_usable_policy, target=flow, defaults={"order": 0}
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=prompt_stage,
|
||||
defaults={
|
||||
"order": 10,
|
||||
},
|
||||
)
|
||||
user_write_binding, _ = FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=user_write,
|
||||
defaults={"order": 20, "evaluate_on_plan": False, "re_evaluate_policies": True},
|
||||
)
|
||||
PolicyBinding.objects.using(db_alias).update_or_create(
|
||||
policy=prefill_policy, target=user_write_binding, defaults={"order": 0}
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=login_stage,
|
||||
defaults={
|
||||
"order": 100,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0017_auto_20210329_1334"),
|
||||
("authentik_stages_user_write", "0002_auto_20200918_1653"),
|
||||
("authentik_stages_user_login", "0003_session_duration_delta"),
|
||||
("authentik_stages_password", "0002_passwordstage_change_flow"),
|
||||
("authentik_policies", "0001_initial"),
|
||||
("authentik_policies_expression", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_oobe_flow),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,21 +1,5 @@
|
|||
# Generated by Django 4.0 on 2021-12-27 21:03
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def update_title_for_defaults(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
slug_title_map = {
|
||||
"default-provider-authorization-implicit-consent": "Redirecting to %(app)s",
|
||||
"default-provider-authorization-explicit-consent": "Redirecting to %(app)s",
|
||||
}
|
||||
db_alias = schema_editor.connection.alias
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
for flow in Flow.objects.using(db_alias).all():
|
||||
if flow.slug not in slug_title_map:
|
||||
continue
|
||||
flow.title = slug_title_map[flow.slug]
|
||||
flow.save()
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -24,4 +8,4 @@ class Migration(migrations.Migration):
|
|||
("authentik_flows", "0020_flowtoken"),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(update_title_for_defaults)]
|
||||
operations = []
|
||||
|
|
|
@ -87,10 +87,6 @@ def sentry_init(**sentry_init_kwargs):
|
|||
set_tag("authentik.build_hash", get_build_hash("tagged"))
|
||||
set_tag("authentik.env", get_env())
|
||||
set_tag("authentik.component", "backend")
|
||||
LOGGER.info(
|
||||
"Error reporting is enabled",
|
||||
env=kwargs["environment"],
|
||||
)
|
||||
|
||||
|
||||
def traces_sampler(sampling_context: dict) -> float:
|
||||
|
|
|
@ -1,23 +1,6 @@
|
|||
# Generated by Django 3.2.6 on 2021-09-09 11:24
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_defaults(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.providers.oauth2.models import JWTAlgorithms
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
try:
|
||||
for provider in ProxyProvider.objects.using(db_alias).filter(jwt_alg=JWTAlgorithms.RS256):
|
||||
provider.set_oauth_defaults()
|
||||
provider.save()
|
||||
except FieldError:
|
||||
# If the jwt_alg field doesn't exist, just ignore this migration
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -26,4 +9,4 @@ class Migration(migrations.Migration):
|
|||
("authentik_providers_proxy", "0013_mode"),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(migrate_defaults)]
|
||||
operations = []
|
||||
|
|
|
@ -1,29 +1,7 @@
|
|||
# Generated by Django 3.1.7 on 2021-03-23 22:09
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
|
||||
|
||||
def create_default_pre_authentication_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
SAMLSource = apps.get_model("authentik_sources_saml", "samlsource")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
# Empty flow for providers where consent is implicitly given
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-source-pre-authentication",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={"name": "Pre-Authentication", "title": ""},
|
||||
)
|
||||
|
||||
for source in SAMLSource.objects.using(db_alias).all():
|
||||
source.pre_authentication_flow = flow
|
||||
source.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -47,5 +25,4 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(create_default_pre_authentication_flow),
|
||||
]
|
||||
|
|
|
@ -1,42 +1,5 @@
|
|||
# Generated by Django 3.1.1 on 2020-09-25 14:32
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
|
||||
|
||||
def create_default_setup_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
AuthenticatorStaticStage = apps.get_model(
|
||||
"authentik_stages_authenticator_static", "AuthenticatorStaticStage"
|
||||
)
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-authenticator-static-setup",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={
|
||||
"name": "default-authenticator-static-setup",
|
||||
"title": "Setup Static OTP Tokens",
|
||||
},
|
||||
)
|
||||
|
||||
stage, _ = AuthenticatorStaticStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authenticator-static-setup", defaults={"token_count": 6}
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=stage, defaults={"order": 0}
|
||||
)
|
||||
|
||||
for stage in AuthenticatorStaticStage.objects.using(db_alias).filter(configure_flow=None):
|
||||
stage.configure_flow = flow
|
||||
stage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -48,6 +11,4 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_setup_flow),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,43 +1,5 @@
|
|||
# Generated by Django 3.1.1 on 2020-09-25 15:36
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.stages.authenticator_totp.models import TOTPDigits
|
||||
|
||||
|
||||
def create_default_setup_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
AuthenticatorTOTPStage = apps.get_model(
|
||||
"authentik_stages_authenticator_totp", "AuthenticatorTOTPStage"
|
||||
)
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-authenticator-totp-setup",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={
|
||||
"name": "default-authenticator-totp-setup",
|
||||
"title": "Setup Two-Factor authentication",
|
||||
},
|
||||
)
|
||||
|
||||
stage, _ = AuthenticatorTOTPStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authenticator-totp-setup", defaults={"digits": TOTPDigits.SIX}
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=stage, defaults={"order": 0}
|
||||
)
|
||||
|
||||
for stage in AuthenticatorTOTPStage.objects.using(db_alias).filter(configure_flow=None):
|
||||
stage.configure_flow = flow
|
||||
stage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -49,6 +11,4 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_setup_flow),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,57 +1,15 @@
|
|||
# Generated by Django 3.0.3 on 2020-05-08 14:30
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.stages.authenticator_validate.models import default_device_classes
|
||||
|
||||
|
||||
def create_default_validate_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
AuthenticatorValidateStage = apps.get_model(
|
||||
"authentik_stages_authenticator_validate", "AuthenticatorValidateStage"
|
||||
)
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
auth_flows = Flow.objects.using(db_alias).filter(slug="default-authentication-flow")
|
||||
if not auth_flows.exists():
|
||||
return
|
||||
|
||||
# If there's already a validation stage in the flow, skip
|
||||
if (
|
||||
AuthenticatorValidateStage.objects.using(db_alias)
|
||||
.filter(flow__slug="default-authentication-flow")
|
||||
.exists()
|
||||
):
|
||||
return
|
||||
|
||||
validate_stage, _ = AuthenticatorValidateStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authentication-mfa-validation",
|
||||
defaults={
|
||||
"device_classes": default_device_classes,
|
||||
},
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=auth_flows.first(),
|
||||
stage=validate_stage,
|
||||
defaults={
|
||||
"order": 30,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0008_default_flows"),
|
||||
(
|
||||
"authentik_stages_authenticator_validate",
|
||||
"0008_alter_authenticatorvalidatestage_device_classes",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(create_default_validate_stage)]
|
||||
operations = []
|
||||
|
|
|
@ -1,42 +1,6 @@
|
|||
# Generated by Django 3.1.1 on 2020-09-25 14:32
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
|
||||
|
||||
def create_default_setup_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
AuthenticateWebAuthnStage = apps.get_model(
|
||||
"authentik_stages_authenticator_webauthn", "AuthenticateWebAuthnStage"
|
||||
)
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-authenticator-webauthn-setup",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={
|
||||
"name": "default-authenticator-webauthn-setup",
|
||||
"title": "Setup WebAuthn",
|
||||
},
|
||||
)
|
||||
|
||||
stage, _ = AuthenticateWebAuthnStage.objects.using(db_alias).update_or_create(
|
||||
name="default-authenticator-webauthn-setup"
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=stage, defaults={"order": 0}
|
||||
)
|
||||
|
||||
for stage in AuthenticateWebAuthnStage.objects.using(db_alias).filter(configure_flow=None):
|
||||
stage.configure_flow = flow
|
||||
stage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -48,6 +12,4 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_setup_flow),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,95 +1,13 @@
|
|||
# Generated by Django 3.0.7 on 2020-06-29 08:51
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.stages.prompt.models import FieldTypes
|
||||
|
||||
|
||||
def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
PromptStage = apps.get_model("authentik_stages_prompt", "PromptStage")
|
||||
Prompt = apps.get_model("authentik_stages_prompt", "Prompt")
|
||||
|
||||
UserWriteStage = apps.get_model("authentik_stages_user_write", "UserWriteStage")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-password-change",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={"name": "Change Password"},
|
||||
)
|
||||
|
||||
prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create(
|
||||
name="default-password-change-prompt",
|
||||
)
|
||||
password_prompt, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="password",
|
||||
defaults={
|
||||
"label": "Password",
|
||||
"type": FieldTypes.PASSWORD,
|
||||
"required": True,
|
||||
"placeholder": "Password",
|
||||
"order": 300,
|
||||
},
|
||||
)
|
||||
password_rep_prompt, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="password_repeat",
|
||||
defaults={
|
||||
"label": "Password (repeat)",
|
||||
"type": FieldTypes.PASSWORD,
|
||||
"required": True,
|
||||
"placeholder": "Password (repeat)",
|
||||
"order": 301,
|
||||
},
|
||||
)
|
||||
|
||||
prompt_stage.fields.add(password_prompt)
|
||||
prompt_stage.fields.add(password_rep_prompt)
|
||||
prompt_stage.save()
|
||||
|
||||
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
|
||||
name="default-password-change-write"
|
||||
)
|
||||
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=prompt_stage, defaults={"order": 0}
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow, stage=user_write, defaults={"order": 1}
|
||||
)
|
||||
|
||||
|
||||
def update_default_stage_change(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
PasswordStage = apps.get_model("authentik_stages_password", "PasswordStage")
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
|
||||
flow = Flow.objects.get(
|
||||
slug="default-password-change",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
)
|
||||
|
||||
stages = PasswordStage.objects.filter(name="default-authentication-password")
|
||||
if not stages.exists():
|
||||
return
|
||||
stage = stages.first()
|
||||
stage.change_flow = flow
|
||||
stage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0008_default_flows"),
|
||||
("authentik_stages_password", "0001_initial"),
|
||||
("authentik_stages_prompt", "0001_initial"),
|
||||
("authentik_stages_user_write", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -104,6 +22,4 @@ class Migration(migrations.Migration):
|
|||
to="authentik_flows.Flow",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(create_default_password_change),
|
||||
migrations.RunPython(update_default_stage_change),
|
||||
]
|
||||
|
|
|
@ -1,21 +1,5 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-21 13:12
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def rename_default_prompt_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
PromptStage = apps.get_model("authentik_stages_prompt", "PromptStage")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
stages = PromptStage.objects.using(db_alias).filter(name="Change your password")
|
||||
if not stages.exists():
|
||||
return
|
||||
stage = stages.first()
|
||||
if PromptStage.objects.using(db_alias).filter(name="default-password-change-prompt").exists():
|
||||
return
|
||||
stage.name = "default-password-change-prompt"
|
||||
stage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -24,6 +8,4 @@ class Migration(migrations.Migration):
|
|||
("authentik_stages_password", "0005_auto_20210402_2221"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(rename_default_prompt_stage),
|
||||
]
|
||||
operations = []
|
||||
|
|
|
@ -1,160 +1,7 @@
|
|||
# Generated by Django 4.0.2 on 2022-02-26 21:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
from authentik.flows.models import FlowDesignation
|
||||
from authentik.stages.identification.models import UserFields
|
||||
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
|
||||
|
||||
AUTHORIZATION_POLICY = """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
|
||||
"""
|
||||
|
||||
|
||||
def create_default_user_settings_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.stages.prompt.models import FieldTypes
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
Tenant = apps.get_model("authentik_tenants", "Tenant")
|
||||
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding")
|
||||
|
||||
ExpressionPolicy = apps.get_model("authentik_policies_expression", "ExpressionPolicy")
|
||||
|
||||
UserWriteStage = apps.get_model("authentik_stages_user_write", "UserWriteStage")
|
||||
PromptStage = apps.get_model("authentik_stages_prompt", "PromptStage")
|
||||
Prompt = apps.get_model("authentik_stages_prompt", "Prompt")
|
||||
|
||||
prompt_username, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="username",
|
||||
order=200,
|
||||
defaults={
|
||||
"label": "Username",
|
||||
"type": FieldTypes.TEXT,
|
||||
"placeholder": """try:
|
||||
return user.username
|
||||
except:
|
||||
return ''""",
|
||||
"placeholder_expression": True,
|
||||
},
|
||||
)
|
||||
prompt_name, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="name",
|
||||
order=201,
|
||||
defaults={
|
||||
"label": "Name",
|
||||
"type": FieldTypes.TEXT,
|
||||
"placeholder": """try:
|
||||
return user.name
|
||||
except:
|
||||
return ''""",
|
||||
"placeholder_expression": True,
|
||||
},
|
||||
)
|
||||
prompt_email, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="email",
|
||||
order=202,
|
||||
defaults={
|
||||
"label": "Email",
|
||||
"type": FieldTypes.EMAIL,
|
||||
"placeholder": """try:
|
||||
return user.email
|
||||
except:
|
||||
return ''""",
|
||||
"placeholder_expression": True,
|
||||
},
|
||||
)
|
||||
prompt_locale, _ = Prompt.objects.using(db_alias).update_or_create(
|
||||
field_key="attributes.settings.locale",
|
||||
order=203,
|
||||
defaults={
|
||||
"label": "Locale",
|
||||
"type": FieldTypes.AK_LOCALE,
|
||||
"placeholder": """try:
|
||||
return user.attributes.get("settings", {}).get("locale", "")
|
||||
except:
|
||||
return ''""",
|
||||
"placeholder_expression": True,
|
||||
"required": True,
|
||||
},
|
||||
)
|
||||
|
||||
validation_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
|
||||
name="default-user-settings-authorization",
|
||||
defaults={
|
||||
"expression": AUTHORIZATION_POLICY,
|
||||
},
|
||||
)
|
||||
prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create(
|
||||
name="default-user-settings",
|
||||
)
|
||||
prompt_stage.validation_policies.set([validation_policy])
|
||||
prompt_stage.fields.set([prompt_username, prompt_name, prompt_email, prompt_locale])
|
||||
prompt_stage.save()
|
||||
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
|
||||
name="default-user-settings-write"
|
||||
)
|
||||
|
||||
flow, _ = Flow.objects.using(db_alias).update_or_create(
|
||||
slug="default-user-settings-flow",
|
||||
designation=FlowDesignation.STAGE_CONFIGURATION,
|
||||
defaults={
|
||||
"name": "Update your info",
|
||||
},
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=prompt_stage,
|
||||
defaults={
|
||||
"order": 20,
|
||||
},
|
||||
)
|
||||
FlowStageBinding.objects.using(db_alias).update_or_create(
|
||||
target=flow,
|
||||
stage=user_write,
|
||||
defaults={
|
||||
"order": 100,
|
||||
},
|
||||
)
|
||||
|
||||
tenant = Tenant.objects.using(db_alias).filter(default=True).first()
|
||||
if not tenant:
|
||||
return
|
||||
tenant.flow_user_settings = flow
|
||||
tenant.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -177,5 +24,4 @@ class Migration(migrations.Migration):
|
|||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(create_default_user_settings_flow),
|
||||
]
|
||||
|
|
117
blueprints/default/40-events-default.yaml
Normal file
117
blueprints/default/40-events-default.yaml
Normal file
|
@ -0,0 +1,117 @@
|
|||
version: 1
|
||||
metadata:
|
||||
name: Default - Events Transport & Rules
|
||||
entries:
|
||||
- model: authentik_events.notificationtransport
|
||||
id: default-email-transport
|
||||
attrs:
|
||||
mode: email
|
||||
identifiers:
|
||||
name: default-email-transport
|
||||
- model: authentik_events.notificationtransport
|
||||
id: default-local-transport
|
||||
attrs:
|
||||
mode: local
|
||||
identifiers:
|
||||
name: default-local-transport
|
||||
- model: authentik_core.group
|
||||
id: group
|
||||
identifiers:
|
||||
name: authentik Admins
|
||||
attrs:
|
||||
is_superuser: true
|
||||
users: []
|
||||
parent: null
|
||||
|
||||
- model: authentik_policies_event_matcher.eventmatcherpolicy
|
||||
id: default-match-configuration-error
|
||||
attrs:
|
||||
action: configuration_error
|
||||
identifiers:
|
||||
name: default-match-configuration-error
|
||||
- model: authentik_events.notificationrule
|
||||
id: default-notify-configuration-error
|
||||
identifiers:
|
||||
name: default-notify-configuration-error
|
||||
attrs:
|
||||
severity: alert
|
||||
group: !KeyOf group
|
||||
transports:
|
||||
- !KeyOf default-email-transport
|
||||
- !KeyOf default-local-transport
|
||||
- model: authentik_policies.policybinding
|
||||
attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
timeout: 30
|
||||
identifiers:
|
||||
order: 0
|
||||
policy: !KeyOf default-match-configuration-error
|
||||
target: !KeyOf default-notify-configuration-error
|
||||
|
||||
- model: authentik_policies_event_matcher.eventmatcherpolicy
|
||||
id: default-match-update
|
||||
attrs:
|
||||
action: update_available
|
||||
identifiers:
|
||||
name: default-match-update
|
||||
- model: authentik_events.notificationrule
|
||||
id: default-notify-update
|
||||
identifiers:
|
||||
name: default-notify-update
|
||||
attrs:
|
||||
severity: alert
|
||||
group: !KeyOf group
|
||||
transports:
|
||||
- !KeyOf default-email-transport
|
||||
- !KeyOf default-local-transport
|
||||
- model: authentik_policies.policybinding
|
||||
attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
timeout: 30
|
||||
identifiers:
|
||||
order: 0
|
||||
policy: !KeyOf default-match-update
|
||||
target: !KeyOf default-notify-update
|
||||
|
||||
- model: authentik_policies_event_matcher.eventmatcherpolicy
|
||||
id: default-match-policy-exception
|
||||
attrs:
|
||||
action: policy_exception
|
||||
identifiers:
|
||||
name: default-match-policy-exception
|
||||
- model: authentik_policies_event_matcher.eventmatcherpolicy
|
||||
id: default-match-property-mapping-exception
|
||||
attrs:
|
||||
action: property_mapping_exception
|
||||
identifiers:
|
||||
name: default-match-property-mapping-exception
|
||||
- model: authentik_events.notificationrule
|
||||
id: default-notify-exception
|
||||
identifiers:
|
||||
name: default-notify-exception
|
||||
attrs:
|
||||
severity: alert
|
||||
group: !KeyOf group
|
||||
transports:
|
||||
- !KeyOf default-email-transport
|
||||
- !KeyOf default-local-transport
|
||||
- model: authentik_policies.policybinding
|
||||
attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
timeout: 30
|
||||
identifiers:
|
||||
order: 0
|
||||
policy: !KeyOf default-match-policy-exception
|
||||
target: !KeyOf default-notify-exception
|
||||
- model: authentik_policies.policybinding
|
||||
attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
timeout: 30
|
||||
identifiers:
|
||||
order: 1
|
||||
policy: !KeyOf default-match-property-mapping-exception
|
||||
target: !KeyOf default-notify-exception
|
|
@ -5,6 +5,7 @@ entries:
|
|||
- attrs:
|
||||
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
|
||||
flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
|
||||
identifiers:
|
||||
domain: authentik-default
|
||||
default: True
|
||||
|
|
161
blueprints/default/91-flow-oobe.yaml
Normal file
161
blueprints/default/91-flow-oobe.yaml
Normal file
|
@ -0,0 +1,161 @@
|
|||
metadata:
|
||||
name: Default - Out-of-box-experience flow
|
||||
version: 1
|
||||
entries:
|
||||
- attrs:
|
||||
compatibility_mode: false
|
||||
denied_action: message_continue
|
||||
designation: stage_configuration
|
||||
name: default-oobe-setup
|
||||
policy_engine_mode: all
|
||||
title: Welcome to authentik!
|
||||
id: flow
|
||||
identifiers:
|
||||
slug: initial-setup
|
||||
model: authentik_flows.flow
|
||||
- attrs:
|
||||
order: 100
|
||||
placeholder: Welcome to authentik! Please set a password for the default admin
|
||||
user, akadmin.
|
||||
placeholder_expression: false
|
||||
required: true
|
||||
sub_text: ''
|
||||
type: static
|
||||
id: prompt-field-header
|
||||
identifiers:
|
||||
field_key: oobe-header-text
|
||||
label: oobe-header-text
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
order: 101
|
||||
placeholder: Admin email
|
||||
placeholder_expression: false
|
||||
required: true
|
||||
sub_text: ''
|
||||
type: email
|
||||
id: prompt-field-email
|
||||
identifiers:
|
||||
field_key: email
|
||||
label: Email
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
order: 300
|
||||
placeholder: Password
|
||||
placeholder_expression: false
|
||||
required: true
|
||||
sub_text: ''
|
||||
type: password
|
||||
id: prompt-field-password
|
||||
identifiers:
|
||||
field_key: password
|
||||
label: Password
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
order: 301
|
||||
placeholder: Password (repeat)
|
||||
placeholder_expression: false
|
||||
required: true
|
||||
sub_text: ''
|
||||
type: password
|
||||
id: prompt-field-password-repeat
|
||||
identifiers:
|
||||
field_key: password_repeat
|
||||
label: Password (repeat)
|
||||
model: authentik_stages_prompt.prompt
|
||||
- attrs:
|
||||
execution_logging: false
|
||||
expression: |
|
||||
# This policy sets the user for the currently running flow
|
||||
# by injecting "pending_user"
|
||||
akadmin = ak_user_by(username="akadmin")
|
||||
context["flow_plan"].context["pending_user"] = akadmin
|
||||
return True
|
||||
id: policy-default-oobe-prefill-user
|
||||
identifiers:
|
||||
name: default-oobe-prefill-user
|
||||
model: authentik_policies_expression.expressionpolicy
|
||||
- attrs:
|
||||
execution_logging: false
|
||||
expression: |
|
||||
# This policy ensures that the setup flow can only be
|
||||
# executed when the admin user doesn''t have a password set
|
||||
akadmin = ak_user_by(username="akadmin")
|
||||
return not akadmin.has_usable_password()
|
||||
id: policy-default-oobe-password-usable
|
||||
identifiers:
|
||||
name: default-oobe-password-usable
|
||||
model: authentik_policies_expression.expressionpolicy
|
||||
- attrs:
|
||||
fields:
|
||||
- !KeyOf prompt-field-header
|
||||
- !KeyOf prompt-field-email
|
||||
- !KeyOf prompt-field-password
|
||||
- !KeyOf prompt-field-password-repeat
|
||||
validation_policies: []
|
||||
id: stage-default-oobe-password
|
||||
identifiers:
|
||||
name: stage-default-oobe-password
|
||||
model: authentik_stages_prompt.promptstage
|
||||
- attrs:
|
||||
session_duration: seconds=0
|
||||
id: stage-default-authentication-login
|
||||
identifiers:
|
||||
name: default-authentication-login
|
||||
model: authentik_stages_user_login.userloginstage
|
||||
- attrs:
|
||||
create_users_as_inactive: false
|
||||
create_users_group: null
|
||||
user_path_template: ''
|
||||
id: stage-default-password-change-write
|
||||
identifiers:
|
||||
name: 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: 10
|
||||
stage: !KeyOf stage-default-oobe-password
|
||||
target: !KeyOf flow
|
||||
model: authentik_flows.flowstagebinding
|
||||
- attrs:
|
||||
evaluate_on_plan: false
|
||||
invalid_response_action: retry
|
||||
policy_engine_mode: all
|
||||
re_evaluate_policies: true
|
||||
id: binding-password-write
|
||||
identifiers:
|
||||
order: 20
|
||||
stage: !KeyOf stage-default-password-change-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: 100
|
||||
stage: !KeyOf stage-default-authentication-login
|
||||
target: !KeyOf flow
|
||||
model: authentik_flows.flowstagebinding
|
||||
- attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
timeout: 30
|
||||
identifiers:
|
||||
order: 0
|
||||
policy: !KeyOf policy-default-oobe-password-usable
|
||||
target: !KeyOf flow
|
||||
model: authentik_policies.policybinding
|
||||
- attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
timeout: 30
|
||||
identifiers:
|
||||
order: 0
|
||||
policy: !KeyOf policy-default-oobe-prefill-user
|
||||
target: !KeyOf binding-password-write
|
||||
model: authentik_policies.policybinding
|
162
blueprints/schema.json
Normal file
162
blueprints/schema.json
Normal file
|
@ -0,0 +1,162 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "http://example.com/example.json",
|
||||
"type": "object",
|
||||
"title": "authentik Blueprint schema",
|
||||
"default": {},
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"$id": "#/properties/version",
|
||||
"type": "integer",
|
||||
"title": "Blueprint version",
|
||||
"default": 1
|
||||
},
|
||||
"metadata": {
|
||||
"$id": "#/properties/metadata",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$id": "#entry",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"identifiers"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auth.permission",
|
||||
"contenttypes.contenttype",
|
||||
"sessions.session",
|
||||
"authentik_crypto.certificatekeypair",
|
||||
"authentik_events.event",
|
||||
"authentik_events.notificationtransport",
|
||||
"authentik_events.notification",
|
||||
"authentik_events.notificationrule",
|
||||
"authentik_events.notificationwebhookmapping",
|
||||
"authentik_flows.flow",
|
||||
"authentik_flows.flowstagebinding",
|
||||
"authentik_flows.flowtoken",
|
||||
"authentik_outposts.dockerserviceconnection",
|
||||
"authentik_outposts.kubernetesserviceconnection",
|
||||
"authentik_outposts.outpost",
|
||||
"authentik_policies_dummy.dummypolicy",
|
||||
"authentik_policies_event_matcher.eventmatcherpolicy",
|
||||
"authentik_policies_expiry.passwordexpirypolicy",
|
||||
"authentik_policies_expression.expressionpolicy",
|
||||
"authentik_policies_hibp.haveibeenpwendpolicy",
|
||||
"authentik_policies_password.passwordpolicy",
|
||||
"authentik_policies_reputation.reputationpolicy",
|
||||
"authentik_policies_reputation.reputation",
|
||||
"authentik_policies.policybinding",
|
||||
"authentik_providers_ldap.ldapprovider",
|
||||
"authentik_providers_oauth2.scopemapping",
|
||||
"authentik_providers_oauth2.oauth2provider",
|
||||
"authentik_providers_oauth2.authorizationcode",
|
||||
"authentik_providers_oauth2.refreshtoken",
|
||||
"authentik_providers_proxy.proxyprovider",
|
||||
"authentik_providers_saml.samlprovider",
|
||||
"authentik_providers_saml.samlpropertymapping",
|
||||
"authentik_sources_ldap.ldapsource",
|
||||
"authentik_sources_ldap.ldappropertymapping",
|
||||
"authentik_sources_oauth.oauthsource",
|
||||
"authentik_sources_oauth.useroauthsourceconnection",
|
||||
"authentik_sources_plex.plexsource",
|
||||
"authentik_sources_plex.plexsourceconnection",
|
||||
"authentik_sources_saml.samlsource",
|
||||
"authentik_stages_authenticator_duo.authenticatorduostage",
|
||||
"authentik_stages_authenticator_duo.duodevice",
|
||||
"authentik_stages_authenticator_sms.authenticatorsmsstage",
|
||||
"authentik_stages_authenticator_sms.smsdevice",
|
||||
"authentik_stages_authenticator_static.authenticatorstaticstage",
|
||||
"authentik_stages_authenticator_totp.authenticatortotpstage",
|
||||
"authentik_stages_authenticator_validate.authenticatorvalidatestage",
|
||||
"authentik_stages_authenticator_webauthn.authenticatewebauthnstage",
|
||||
"authentik_stages_authenticator_webauthn.webauthndevice",
|
||||
"authentik_stages_captcha.captchastage",
|
||||
"authentik_stages_consent.consentstage",
|
||||
"authentik_stages_consent.userconsent",
|
||||
"authentik_stages_deny.denystage",
|
||||
"authentik_stages_dummy.dummystage",
|
||||
"authentik_stages_email.emailstage",
|
||||
"authentik_stages_identification.identificationstage",
|
||||
"authentik_stages_invitation.invitationstage",
|
||||
"authentik_stages_invitation.invitation",
|
||||
"authentik_stages_password.passwordstage",
|
||||
"authentik_stages_prompt.prompt",
|
||||
"authentik_stages_prompt.promptstage",
|
||||
"authentik_stages_user_delete.userdeletestage",
|
||||
"authentik_stages_user_login.userloginstage",
|
||||
"authentik_stages_user_logout.userlogoutstage",
|
||||
"authentik_stages_user_write.userwritestage",
|
||||
"authentik_tenants.tenant",
|
||||
"authentik_blueprints.blueprintinstance",
|
||||
"guardian.userobjectpermission",
|
||||
"guardian.groupobjectpermission",
|
||||
"otp_static.staticdevice",
|
||||
"otp_static.statictoken",
|
||||
"otp_totp.totpdevice",
|
||||
"silk.request",
|
||||
"silk.response",
|
||||
"silk.sqlquery",
|
||||
"silk.profile",
|
||||
"authentik_core.group",
|
||||
"authentik_core.user",
|
||||
"authentik_core.application",
|
||||
"authentik_core.token"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Commonly available field, may not exist on all models"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"identifiers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pk": {
|
||||
"description": "Commonly available field, may not exist on all models",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ elif [[ "$1" == "worker" ]]; then
|
|||
check_if_root "celery -A authentik.root.celery worker -Ofair --max-tasks-per-child=1 --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events"
|
||||
elif [[ "$1" == "bash" ]]; then
|
||||
/bin/bash
|
||||
elif [[ "$1" == "test" ]]; then
|
||||
elif [[ "$1" == "test-all" ]]; then
|
||||
pip install --no-cache-dir -r /requirements-dev.txt
|
||||
touch /unittest.xml
|
||||
chown authentik:authentik /unittest.xml
|
||||
|
|
|
@ -218,9 +218,6 @@ export class AdminInterface extends LitElement {
|
|||
<ak-sidebar-item path="/outpost/outposts">
|
||||
<span slot="label">${t`Outposts`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/outpost/integrations">
|
||||
<span slot="label">${t`Outpost Integrations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Events`}</span>
|
||||
|
@ -248,6 +245,9 @@ export class AdminInterface extends LitElement {
|
|||
<ak-sidebar-item path="/core/property-mappings">
|
||||
<span slot="label">${t`Property Mappings`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/blueprints/instances">
|
||||
<span slot="label">${t`Blueprints`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item>
|
||||
<span slot="label">${t`Flows & Stages`}</span>
|
||||
|
@ -300,8 +300,8 @@ export class AdminInterface extends LitElement {
|
|||
<ak-sidebar-item path="/crypto/certificates">
|
||||
<span slot="label">${t`Certificates`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/blueprints/instances">
|
||||
<span slot="label">${t`Blueprints`}</span>
|
||||
<ak-sidebar-item path="/outpost/integrations">
|
||||
<span slot="label">${t`Outpost Integrations`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
`;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { t } from "@lingui/macro";
|
|||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import { BlueprintInstance, ManagedApi } from "@goauthentik/api";
|
||||
|
@ -48,7 +49,7 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
|||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${first(this.instance?.name)}"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
|
|
Reference in a new issue