blueprints/cleanup (#3369)

This commit is contained in:
Jens L 2022-08-05 08:39:00 +02:00 committed by GitHub
parent 2a5a975d9a
commit ec42d378ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 679 additions and 1121 deletions

View File

@ -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

View File

@ -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"
}
} }

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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
}
}
}
}
}
}

View File

@ -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()

View File

@ -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))

View 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"
),
)

View File

@ -35,7 +35,16 @@ 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 = (
def is_model_allowed(model: type[Model]) -> bool:
"""Check if model is allowed"""
# pylint: disable=imported-auth-user
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.auth.models import User as DjangoUser
excluded_models = (
DjangoUser,
DjangoGroup,
# Base classes # Base classes
Provider, Provider,
Source, Source,
@ -48,6 +57,7 @@ EXCLUDED_MODELS = (
# Classes that have other dependencies # Classes that have other dependencies
AuthenticatedSession, 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()

View File

@ -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:

View File

@ -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),
] ]

View File

@ -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),
]

View File

@ -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__,

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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 = []

View File

@ -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",

View File

@ -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),
] ]

View File

@ -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),
]

View File

@ -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 = []

View File

@ -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:

View File

@ -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 = []

View File

@ -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),
] ]

View File

@ -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),
]

View File

@ -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),
]

View File

@ -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 = []

View File

@ -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),
]

View File

@ -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),
] ]

View File

@ -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),
]

View File

@ -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),
] ]

View 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

View File

@ -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

View 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
View 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
}
}
}
}
}
}

View File

@ -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

View File

@ -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>
`; `;

View File

@ -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
/> />