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
|
echo "AUTHENTIK_TAG=latest" >> .env
|
||||||
docker-compose up --no-start
|
docker-compose up --no-start
|
||||||
docker-compose start postgresql redis
|
docker-compose start postgresql redis
|
||||||
docker-compose run -u root server test
|
docker-compose run -u root server test-all
|
||||||
- name: Extract version number
|
- name: Extract version number
|
||||||
id: get_version
|
id: get_version
|
||||||
uses: actions/github-script@v6
|
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.importModuleSpecifier": "non-relative",
|
||||||
"typescript.preferences.importModuleSpecifierEnding": "index",
|
"typescript.preferences.importModuleSpecifierEnding": "index",
|
||||||
"typescript.tsdk": "./web/node_modules/typescript/lib",
|
"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: i18n-extract-core web-extract
|
||||||
|
|
||||||
i18n-extract-core:
|
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:
|
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:
|
gen-clean:
|
||||||
rm -rf web/api/src/
|
rm -rf web/api/src/
|
||||||
|
@ -168,7 +169,7 @@ ci-pyright: ci--meta-debug
|
||||||
pyright e2e lifecycle
|
pyright e2e lifecycle
|
||||||
|
|
||||||
ci-pending-migrations: ci--meta-debug
|
ci-pending-migrations: ci--meta-debug
|
||||||
./manage.py makemigrations --check
|
ak makemigrations --check
|
||||||
|
|
||||||
install: web-install website-install
|
install: web-install website-install
|
||||||
poetry 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.blueprints.v1.tasks import BlueprintFile, apply_blueprint, blueprints_find
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
from authentik.events.utils import sanitize_dict
|
||||||
|
|
||||||
|
|
||||||
class ManagedSerializer:
|
class ManagedSerializer:
|
||||||
|
@ -85,7 +86,7 @@ class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
|
||||||
def available(self, request: Request) -> Response:
|
def available(self, request: Request) -> Response:
|
||||||
"""Get blueprints"""
|
"""Get blueprints"""
|
||||||
files: list[BlueprintFile] = blueprints_find.delay().get()
|
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")
|
@permission_required("authentik_blueprints.view_blueprintinstance")
|
||||||
@extend_schema(
|
@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 django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
from yaml import load
|
from yaml import load
|
||||||
|
|
||||||
|
from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_SYSTEM
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ def check_blueprint_v1_file(BlueprintInstance: type["BlueprintInstance"], path:
|
||||||
if not instance:
|
if not instance:
|
||||||
instance = BlueprintInstance(
|
instance = BlueprintInstance(
|
||||||
name=meta.name if meta else str(rel_path),
|
name=meta.name if meta else str(rel_path),
|
||||||
path=str(path),
|
path=str(rel_path),
|
||||||
context={},
|
context={},
|
||||||
status=BlueprintInstanceStatus.UNKNOWN,
|
status=BlueprintInstanceStatus.UNKNOWN,
|
||||||
enabled=True,
|
enabled=True,
|
||||||
|
@ -62,7 +63,7 @@ def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEdit
|
||||||
if Flow.objects.using(db_alias).all().exists():
|
if Flow.objects.using(db_alias).all().exists():
|
||||||
blueprint.enabled = False
|
blueprint.enabled = False
|
||||||
# System blueprints are always enabled
|
# 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.enabled = True
|
||||||
blueprint.save()
|
blueprint.save()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from typing import Callable, Type
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.test import TestCase
|
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
|
from authentik.lib.models import SerializerModel
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +29,6 @@ for app in apps.get_app_configs():
|
||||||
if not app.label.startswith("authentik"):
|
if not app.label.startswith("authentik"):
|
||||||
continue
|
continue
|
||||||
for model in app.get_models():
|
for model in app.get_models():
|
||||||
if model in EXCLUDED_MODELS:
|
if not is_model_allowed(model):
|
||||||
continue
|
continue
|
||||||
setattr(TestModels, f"test_{app.label}_{model.__name__}", serializer_tester_factory(model))
|
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.outposts.models import OutpostServiceConnection
|
||||||
from authentik.policies.models import Policy, PolicyBindingModel
|
from authentik.policies.models import Policy, PolicyBindingModel
|
||||||
|
|
||||||
EXCLUDED_MODELS = (
|
|
||||||
# Base classes
|
def is_model_allowed(model: type[Model]) -> bool:
|
||||||
Provider,
|
"""Check if model is allowed"""
|
||||||
Source,
|
# pylint: disable=imported-auth-user
|
||||||
PropertyMapping,
|
from django.contrib.auth.models import Group as DjangoGroup
|
||||||
UserSourceConnection,
|
from django.contrib.auth.models import User as DjangoUser
|
||||||
Stage,
|
|
||||||
OutpostServiceConnection,
|
excluded_models = (
|
||||||
Policy,
|
DjangoUser,
|
||||||
PolicyBindingModel,
|
DjangoGroup,
|
||||||
# Classes that have other dependencies
|
# Base classes
|
||||||
AuthenticatedSession,
|
Provider,
|
||||||
)
|
Source,
|
||||||
|
PropertyMapping,
|
||||||
|
UserSourceConnection,
|
||||||
|
Stage,
|
||||||
|
OutpostServiceConnection,
|
||||||
|
Policy,
|
||||||
|
PolicyBindingModel,
|
||||||
|
# Classes that have other dependencies
|
||||||
|
AuthenticatedSession,
|
||||||
|
)
|
||||||
|
return model not in excluded_models
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
@ -123,8 +133,10 @@ class Importer:
|
||||||
model_app_label, model_name = entry.model.split(".")
|
model_app_label, model_name = entry.model.split(".")
|
||||||
model: type[SerializerModel] = apps.get_model(model_app_label, model_name)
|
model: type[SerializerModel] = apps.get_model(model_app_label, model_name)
|
||||||
# Don't use isinstance since we don't want to check for inheritance
|
# 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")
|
raise EntryInvalidError(f"Model {model} not allowed")
|
||||||
|
if entry.identifiers == {}:
|
||||||
|
raise EntryInvalidError("No identifiers")
|
||||||
|
|
||||||
# If we try to validate without referencing a possible instance
|
# If we try to validate without referencing a possible instance
|
||||||
# we'll get a duplicate error, hence we load the model here and return
|
# we'll get a duplicate error, hence we load the model here and return
|
||||||
|
@ -148,6 +160,7 @@ class Importer:
|
||||||
pk=model_instance.pk,
|
pk=model_instance.pk,
|
||||||
)
|
)
|
||||||
serializer_kwargs["instance"] = model_instance
|
serializer_kwargs["instance"] = model_instance
|
||||||
|
serializer_kwargs["partial"] = True
|
||||||
else:
|
else:
|
||||||
self.logger.debug("initialise new instance", model=model, **updated_identifiers)
|
self.logger.debug("initialise new instance", model=model, **updated_identifiers)
|
||||||
model_instance = model()
|
model_instance = model()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
"""v1 blueprints tasks"""
|
"""v1 blueprints tasks"""
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from glob import glob
|
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -43,7 +42,8 @@ class BlueprintFile:
|
||||||
def blueprints_find():
|
def blueprints_find():
|
||||||
"""Find blueprints and return valid ones"""
|
"""Find blueprints and return valid ones"""
|
||||||
blueprints = []
|
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)
|
path = Path(file)
|
||||||
with open(path, "r", encoding="utf-8") as blueprint_file:
|
with open(path, "r", encoding="utf-8") as blueprint_file:
|
||||||
try:
|
try:
|
||||||
|
@ -57,7 +57,7 @@ def blueprints_find():
|
||||||
if version != 1:
|
if version != 1:
|
||||||
continue
|
continue
|
||||||
file_hash = sha512(path.read_bytes()).hexdigest()
|
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
|
blueprint.meta = from_dict(BlueprintMetadata, metadata) if metadata else None
|
||||||
if (
|
if (
|
||||||
blueprint.meta
|
blueprint.meta
|
||||||
|
@ -88,11 +88,10 @@ def blueprints_discover(self: MonitoredTask):
|
||||||
|
|
||||||
def check_blueprint_v1_file(blueprint: BlueprintFile):
|
def check_blueprint_v1_file(blueprint: BlueprintFile):
|
||||||
"""Check if blueprint should be imported"""
|
"""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()
|
instance: BlueprintInstance = BlueprintInstance.objects.filter(path=blueprint.path).first()
|
||||||
if not instance:
|
if not instance:
|
||||||
instance = BlueprintInstance(
|
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,
|
path=blueprint.path,
|
||||||
context={},
|
context={},
|
||||||
status=BlueprintInstanceStatus.UNKNOWN,
|
status=BlueprintInstanceStatus.UNKNOWN,
|
||||||
|
@ -119,8 +118,9 @@ def apply_blueprint(self: MonitoredTask, instance_pk: str):
|
||||||
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
|
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
|
||||||
if not instance or not instance.enabled:
|
if not instance or not instance.enabled:
|
||||||
return
|
return
|
||||||
file_hash = sha512(Path(instance.path).read_bytes()).hexdigest()
|
full_path = Path(CONFIG.y("blueprints_dir")).joinpath(Path(instance.path))
|
||||||
with open(instance.path, "r", encoding="utf-8") as blueprint_file:
|
file_hash = sha512(full_path.read_bytes()).hexdigest()
|
||||||
|
with open(full_path, "r", encoding="utf-8") as blueprint_file:
|
||||||
importer = Importer(blueprint_file.read())
|
importer = Importer(blueprint_file.read())
|
||||||
valid, logs = importer.validate()
|
valid, logs = importer.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -1,29 +1,5 @@
|
||||||
# Generated by Django 4.0.4 on 2022-05-30 18:08
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -46,5 +22,4 @@ class Migration(migrations.Migration):
|
||||||
default="local",
|
default="local",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(notify_local_transport),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,130 +1,6 @@
|
||||||
# Generated by Django 3.1.4 on 2021-01-10 18:57
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -134,14 +10,6 @@ class Migration(migrations.Migration):
|
||||||
"authentik_events",
|
"authentik_events",
|
||||||
"0010_notification_notificationtransport_notificationrule",
|
"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 = [
|
operations = []
|
||||||
migrations.RunPython(transport_email_global),
|
|
||||||
migrations.RunPython(notify_configuration_error),
|
|
||||||
migrations.RunPython(notify_update),
|
|
||||||
migrations.RunPython(notify_exception),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""event utilities"""
|
"""event utilities"""
|
||||||
import re
|
import re
|
||||||
from dataclasses import asdict, is_dataclass
|
from dataclasses import asdict, is_dataclass
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
@ -97,6 +98,8 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
|
||||||
continue
|
continue
|
||||||
elif isinstance(value, City):
|
elif isinstance(value, City):
|
||||||
final_dict[key] = GEOIP_READER.city_to_dict(value)
|
final_dict[key] = GEOIP_READER.city_to_dict(value)
|
||||||
|
elif isinstance(value, Path):
|
||||||
|
final_dict[key] = str(value)
|
||||||
elif isinstance(value, type):
|
elif isinstance(value, type):
|
||||||
final_dict[key] = {
|
final_dict[key] = {
|
||||||
"type": value.__name__,
|
"type": value.__name__,
|
||||||
|
|
|
@ -1,103 +1,12 @@
|
||||||
# Generated by Django 3.0.3 on 2020-05-08 14:30
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("authentik_flows", "0007_auto_20200703_2059"),
|
("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 = [
|
operations = []
|
||||||
migrations.RunPython(create_default_authentication_flow),
|
|
||||||
migrations.RunPython(create_default_invalidation_flow),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,151 +1,12 @@
|
||||||
# Generated by Django 3.0.6 on 2020-05-23 15:47
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("authentik_flows", "0008_default_flows"),
|
("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 = [
|
operations = []
|
||||||
migrations.RunPython(create_default_source_enrollment_flow),
|
|
||||||
migrations.RunPython(create_default_source_authentication_flow),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,46 +1,12 @@
|
||||||
# Generated by Django 3.0.6 on 2020-05-24 11:34
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("authentik_flows", "0009_source_flows"),
|
("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
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -45,7 +23,6 @@ class Migration(migrations.Migration):
|
||||||
field=models.TextField(default="", blank=True),
|
field=models.TextField(default="", blank=True),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.RunPython(add_title_for_defaults),
|
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="flow",
|
model_name="flow",
|
||||||
name="title",
|
name="title",
|
||||||
|
|
|
@ -1,27 +1,6 @@
|
||||||
# Generated by Django 3.1.1 on 2020-09-25 23:32
|
# 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 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):
|
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.",
|
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
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("authentik_flows", "0017_auto_20210329_1334"),
|
("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 = [
|
operations = []
|
||||||
migrations.RunPython(create_default_oobe_flow),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,21 +1,5 @@
|
||||||
# Generated by Django 4.0 on 2021-12-27 21:03
|
# Generated by Django 4.0 on 2021-12-27 21:03
|
||||||
from django.apps.registry import Apps
|
from django.db import migrations
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -24,4 +8,4 @@ class Migration(migrations.Migration):
|
||||||
("authentik_flows", "0020_flowtoken"),
|
("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.build_hash", get_build_hash("tagged"))
|
||||||
set_tag("authentik.env", get_env())
|
set_tag("authentik.env", get_env())
|
||||||
set_tag("authentik.component", "backend")
|
set_tag("authentik.component", "backend")
|
||||||
LOGGER.info(
|
|
||||||
"Error reporting is enabled",
|
|
||||||
env=kwargs["environment"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def traces_sampler(sampling_context: dict) -> float:
|
def traces_sampler(sampling_context: dict) -> float:
|
||||||
|
|
|
@ -1,23 +1,6 @@
|
||||||
# Generated by Django 3.2.6 on 2021-09-09 11:24
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -26,4 +9,4 @@ class Migration(migrations.Migration):
|
||||||
("authentik_providers_proxy", "0013_mode"),
|
("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
|
# Generated by Django 3.1.7 on 2021-03-23 22:09
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.apps.registry import Apps
|
|
||||||
from django.db import migrations, models
|
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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -47,5 +25,4 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
preserve_default=False,
|
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
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -48,6 +11,4 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
migrations.RunPython(create_default_setup_flow),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,43 +1,5 @@
|
||||||
# Generated by Django 3.1.1 on 2020-09-25 15:36
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -49,6 +11,4 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
migrations.RunPython(create_default_setup_flow),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,57 +1,15 @@
|
||||||
# Generated by Django 3.0.3 on 2020-05-08 14:30
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("authentik_flows", "0008_default_flows"),
|
|
||||||
(
|
(
|
||||||
"authentik_stages_authenticator_validate",
|
"authentik_stages_authenticator_validate",
|
||||||
"0008_alter_authenticatorvalidatestage_device_classes",
|
"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
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -48,6 +12,4 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
migrations.RunPython(create_default_setup_flow),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,95 +1,13 @@
|
||||||
# Generated by Django 3.0.7 on 2020-06-29 08:51
|
# Generated by Django 3.0.7 on 2020-06-29 08:51
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.apps.registry import Apps
|
|
||||||
from django.db import migrations, models
|
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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("authentik_flows", "0008_default_flows"),
|
|
||||||
("authentik_stages_password", "0001_initial"),
|
("authentik_stages_password", "0001_initial"),
|
||||||
("authentik_stages_prompt", "0001_initial"),
|
|
||||||
("authentik_stages_user_write", "0001_initial"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -104,6 +22,4 @@ class Migration(migrations.Migration):
|
||||||
to="authentik_flows.Flow",
|
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
|
# 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 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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -24,6 +8,4 @@ class Migration(migrations.Migration):
|
||||||
("authentik_stages_password", "0005_auto_20210402_2221"),
|
("authentik_stages_password", "0005_auto_20210402_2221"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
migrations.RunPython(rename_default_prompt_stage),
|
|
||||||
]
|
|
||||||
|
|
|
@ -1,160 +1,7 @@
|
||||||
# Generated by Django 4.0.2 on 2022-02-26 21:14
|
# Generated by Django 4.0.2 on 2022-02-26 21:14
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.apps.registry import Apps
|
|
||||||
from django.db import migrations, models
|
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):
|
class Migration(migrations.Migration):
|
||||||
|
@ -177,5 +24,4 @@ class Migration(migrations.Migration):
|
||||||
to="authentik_flows.flow",
|
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:
|
- attrs:
|
||||||
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||||
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
|
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
|
||||||
|
flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
|
||||||
identifiers:
|
identifiers:
|
||||||
domain: authentik-default
|
domain: authentik-default
|
||||||
default: True
|
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"
|
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
|
elif [[ "$1" == "bash" ]]; then
|
||||||
/bin/bash
|
/bin/bash
|
||||||
elif [[ "$1" == "test" ]]; then
|
elif [[ "$1" == "test-all" ]]; then
|
||||||
pip install --no-cache-dir -r /requirements-dev.txt
|
pip install --no-cache-dir -r /requirements-dev.txt
|
||||||
touch /unittest.xml
|
touch /unittest.xml
|
||||||
chown authentik:authentik /unittest.xml
|
chown authentik:authentik /unittest.xml
|
||||||
|
|
|
@ -218,9 +218,6 @@ export class AdminInterface extends LitElement {
|
||||||
<ak-sidebar-item path="/outpost/outposts">
|
<ak-sidebar-item path="/outpost/outposts">
|
||||||
<span slot="label">${t`Outposts`}</span>
|
<span slot="label">${t`Outposts`}</span>
|
||||||
</ak-sidebar-item>
|
</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>
|
||||||
<ak-sidebar-item>
|
<ak-sidebar-item>
|
||||||
<span slot="label">${t`Events`}</span>
|
<span slot="label">${t`Events`}</span>
|
||||||
|
@ -248,6 +245,9 @@ export class AdminInterface extends LitElement {
|
||||||
<ak-sidebar-item path="/core/property-mappings">
|
<ak-sidebar-item path="/core/property-mappings">
|
||||||
<span slot="label">${t`Property Mappings`}</span>
|
<span slot="label">${t`Property Mappings`}</span>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
|
<ak-sidebar-item path="/blueprints/instances">
|
||||||
|
<span slot="label">${t`Blueprints`}</span>
|
||||||
|
</ak-sidebar-item>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
<ak-sidebar-item>
|
<ak-sidebar-item>
|
||||||
<span slot="label">${t`Flows & Stages`}</span>
|
<span slot="label">${t`Flows & Stages`}</span>
|
||||||
|
@ -300,8 +300,8 @@ export class AdminInterface extends LitElement {
|
||||||
<ak-sidebar-item path="/crypto/certificates">
|
<ak-sidebar-item path="/crypto/certificates">
|
||||||
<span slot="label">${t`Certificates`}</span>
|
<span slot="label">${t`Certificates`}</span>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
<ak-sidebar-item path="/blueprints/instances">
|
<ak-sidebar-item path="/outpost/integrations">
|
||||||
<span slot="label">${t`Blueprints`}</span>
|
<span slot="label">${t`Outpost Integrations`}</span>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
import { until } from "lit/directives/until.js";
|
||||||
|
|
||||||
import { BlueprintInstance, ManagedApi } from "@goauthentik/api";
|
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">
|
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(this.instance?.name)}"
|
value="${ifDefined(this.instance?.name)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
Reference in a new issue