blueprints: migrate from managed (#3338)
* test all bundled blueprints Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix empty title Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix default blueprints Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add script to generate dev config Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * migrate managed to blueprints Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add more to blueprint instance Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * migrated away from ObjectManager Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix lint errors Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * migrate things Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * migrate tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix some tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix a bit more Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix more tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * whops Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix missing name Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * *sigh* Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix more tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * add tasks Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * scheduled Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * run discovery on start Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * oops this test should stay Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
7a05c6faef
commit
a023eee9bf
|
@ -73,7 +73,7 @@ RUN apt-get update && \
|
|||
apt-get clean && \
|
||||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||
mkdir -p /certs /media && \
|
||||
mkdir -p /certs /media /blueprints && \
|
||||
mkdir -p /authentik/.ssh && \
|
||||
chown authentik:authentik /certs /media /authentik/.ssh
|
||||
|
||||
|
@ -82,7 +82,8 @@ COPY ./pyproject.toml /
|
|||
COPY ./xml /xml
|
||||
COPY ./tests /tests
|
||||
COPY ./manage.py /
|
||||
COPY ./blueprints/default /blueprints
|
||||
COPY ./blueprints/default /blueprints/default
|
||||
COPY ./blueprints/system /blueprints/system
|
||||
COPY ./lifecycle/ /lifecycle
|
||||
COPY --from=builder /work/authentik /authentik-proxy
|
||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||
|
|
7
Makefile
7
Makefile
|
@ -33,8 +33,8 @@ test:
|
|||
coverage report
|
||||
|
||||
lint-fix:
|
||||
isort authentik tests lifecycle
|
||||
black authentik tests lifecycle
|
||||
isort authentik tests scripts lifecycle
|
||||
black authentik tests scripts lifecycle
|
||||
codespell -I .github/codespell-words.txt -S 'web/src/locales/**' -w \
|
||||
authentik \
|
||||
internal \
|
||||
|
@ -91,6 +91,9 @@ gen-client-go:
|
|||
go mod edit -replace goauthentik.io/api/v3=./gen-go-api
|
||||
rm -rf config.yaml ./templates/
|
||||
|
||||
gen-dev-config:
|
||||
python -m scripts.generate_config
|
||||
|
||||
gen: gen-build gen-clean gen-client-web
|
||||
|
||||
migrate:
|
||||
|
|
|
@ -16,7 +16,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
"""authentik admin app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from prometheus_client import Gauge, Info
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
PROM_INFO = Info("authentik_version", "Currently running authentik version")
|
||||
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
|
||||
|
||||
|
||||
class AuthentikAdminConfig(AppConfig):
|
||||
class AuthentikAdminConfig(ManagedAppConfig):
|
||||
"""authentik admin app config"""
|
||||
|
||||
name = "authentik.admin"
|
||||
label = "authentik_admin"
|
||||
verbose_name = "authentik Admin"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.admin.signals")
|
||||
def reconcile_load_admin_signals(self):
|
||||
"""Load admin signals"""
|
||||
self.import_module("authentik.admin.signals")
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
"""test admin api"""
|
||||
from json import loads
|
||||
|
||||
from django.apps import apps
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.blueprints.tasks import managed_reconcile
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tasks import clean_expired_models
|
||||
from authentik.events.monitored_tasks import TaskResultStatus
|
||||
|
@ -95,7 +95,6 @@ class TestAdminAPI(TestCase):
|
|||
|
||||
def test_system(self):
|
||||
"""Test system API"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
managed_reconcile() # pylint: disable=no-value-for-parameter
|
||||
apps.get_app_config("authentik_outposts").reconcile_embedded_outpost()
|
||||
response = self.client.get(reverse("authentik_api:admin_system"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
|
@ -65,7 +65,7 @@ def bearer_auth(raw_header: bytes) -> Optional[User]:
|
|||
def token_secret_key(value: str) -> Optional[User]:
|
||||
"""Check if the token is the secret key
|
||||
and return the service account for the managed outpost"""
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
|
||||
if value != settings.SECRET_KEY:
|
||||
return None
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Test API Authentication"""
|
||||
from base64 import b64encode
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
@ -10,7 +11,6 @@ from authentik.api.authentication import bearer_auth
|
|||
from authentik.core.models import USER_ATTRIBUTE_SA, Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.outposts.managed import OutpostManager
|
||||
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
|
||||
|
||||
|
@ -44,7 +44,7 @@ class TestAPIAuth(TestCase):
|
|||
with self.assertRaises(AuthenticationFailed):
|
||||
user = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
||||
|
||||
OutpostManager().run()
|
||||
apps.get_app_config("authentik_outposts").reconcile_embedded_outpost()
|
||||
user = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
||||
self.assertEqual(user.attributes[USER_ATTRIBUTE_SA], True)
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
"""Blueprint helpers"""
|
||||
from functools import wraps
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def apply_blueprint(*files: str):
|
||||
"""Apply blueprint before test"""
|
||||
|
||||
from authentik.blueprints.v1.importer import Importer
|
||||
|
||||
def wrapper_outer(func: Callable):
|
||||
"""Apply blueprint before test"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
for file in files:
|
||||
with open(file, "r+", encoding="utf-8") as _file:
|
||||
Importer(_file.read()).apply()
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return wrapper_outer
|
|
@ -27,13 +27,21 @@ class BlueprintInstanceSerializer(ModelSerializer):
|
|||
|
||||
model = BlueprintInstance
|
||||
fields = [
|
||||
"pk",
|
||||
"name",
|
||||
"path",
|
||||
"context",
|
||||
"last_applied",
|
||||
"last_applied_hash",
|
||||
"status",
|
||||
"enabled",
|
||||
"managed_models",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"last_applied": {"read_only": True},
|
||||
"last_applied_hash": {"read_only": True},
|
||||
"managed_models": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
class BlueprintInstanceViewSet(ModelViewSet):
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
"""authentik Blueprints app"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
|
||||
class AuthentikBlueprintsConfig(AppConfig):
|
||||
class AuthentikBlueprintsConfig(ManagedAppConfig):
|
||||
"""authentik Blueprints app"""
|
||||
|
||||
name = "authentik.blueprints"
|
||||
label = "authentik_blueprints"
|
||||
verbose_name = "authentik Blueprints"
|
||||
default = True
|
||||
|
||||
def ready(self) -> None:
|
||||
from authentik.blueprints.tasks import managed_reconcile
|
||||
def reconcile_load_blueprints_v1_tasks(self):
|
||||
"""Load v1 tasks"""
|
||||
self.import_module("authentik.blueprints.v1.tasks")
|
||||
|
||||
managed_reconcile.delay()
|
||||
def reconcile_blueprints_discover(self):
|
||||
"""Run blueprint discovery"""
|
||||
from authentik.blueprints.v1.tasks import blueprints_discover
|
||||
|
||||
blueprints_discover.delay()
|
||||
|
|
|
@ -13,9 +13,9 @@ class Command(BaseCommand): # pragma: no cover
|
|||
for blueprint_path in options.get("blueprints", []):
|
||||
with open(blueprint_path, "r", encoding="utf8") as blueprint_file:
|
||||
importer = Importer(blueprint_file.read())
|
||||
valid = importer.validate()
|
||||
valid, logs = importer.validate()
|
||||
if not valid:
|
||||
raise ValueError("blueprint invalid")
|
||||
raise ValueError(f"blueprint invalid: {logs}")
|
||||
importer.apply()
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
|
|
@ -1,70 +1,37 @@
|
|||
"""Managed objects manager"""
|
||||
from typing import Callable, Optional
|
||||
from importlib import import_module
|
||||
from inspect import ismethod
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db import DatabaseError, ProgrammingError
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.models import ManagedModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class EnsureOp:
|
||||
"""Ensure operation, executed as part of an ObjectManager run"""
|
||||
class ManagedAppConfig(AppConfig):
|
||||
"""Basic reconciliation logic for apps"""
|
||||
|
||||
_obj: type[ManagedModel]
|
||||
_managed_uid: str
|
||||
_kwargs: dict
|
||||
def ready(self) -> None:
|
||||
self.reconcile()
|
||||
return super().ready()
|
||||
|
||||
def __init__(self, obj: type[ManagedModel], managed_uid: str, **kwargs) -> None:
|
||||
self._obj = obj
|
||||
self._managed_uid = managed_uid
|
||||
self._kwargs = kwargs
|
||||
def import_module(self, path: str):
|
||||
"""Load module"""
|
||||
import_module(path)
|
||||
|
||||
def run(self):
|
||||
"""Do the actual ensure action"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class EnsureExists(EnsureOp):
|
||||
"""Ensure object exists, with kwargs as given values"""
|
||||
|
||||
created_callback: Optional[Callable]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
obj: type[ManagedModel],
|
||||
managed_uid: str,
|
||||
created_callback: Optional[Callable] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(obj, managed_uid, **kwargs)
|
||||
self.created_callback = created_callback
|
||||
|
||||
def run(self):
|
||||
self._kwargs.setdefault("managed", self._managed_uid)
|
||||
obj, created = self._obj.objects.update_or_create(
|
||||
**{
|
||||
"managed": self._managed_uid,
|
||||
"defaults": self._kwargs,
|
||||
}
|
||||
)
|
||||
if created and self.created_callback is not None:
|
||||
self.created_callback(obj)
|
||||
|
||||
|
||||
class ObjectManager:
|
||||
"""Base class for Apps Object manager"""
|
||||
|
||||
def run(self):
|
||||
"""Main entrypoint for tasks, iterate through all implementation of this
|
||||
and execute all operations"""
|
||||
for sub in ObjectManager.__subclasses__():
|
||||
sub_inst = sub()
|
||||
ops = sub_inst.reconcile()
|
||||
LOGGER.debug("Reconciling managed objects", manager=sub.__name__)
|
||||
for operation in ops:
|
||||
operation.run()
|
||||
|
||||
def reconcile(self) -> list[EnsureOp]:
|
||||
"""Method which is implemented in subclass that returns a list of Operations"""
|
||||
raise NotImplementedError
|
||||
def reconcile(self) -> None:
|
||||
"""reconcile ourselves"""
|
||||
prefix = "reconcile_"
|
||||
for meth_name in dir(self):
|
||||
meth = getattr(self, meth_name)
|
||||
if not ismethod(meth):
|
||||
continue
|
||||
if not meth_name.startswith(prefix):
|
||||
continue
|
||||
name = meth_name.replace(prefix, "")
|
||||
try:
|
||||
meth()
|
||||
LOGGER.debug("Successfully reconciled", name=name)
|
||||
except (ProgrammingError, DatabaseError) as exc:
|
||||
LOGGER.debug("Failed to run reconcile", name=name, exc=exc)
|
||||
|
|
|
@ -1,16 +1,37 @@
|
|||
# Generated by Django 4.0.6 on 2022-07-30 22:45
|
||||
# Generated by Django 4.0.6 on 2022-07-31 17:35
|
||||
|
||||
import uuid
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations, models
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migration_blueprint_import(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from authentik.blueprints.v1.tasks import blueprints_discover
|
||||
|
||||
BlueprintInstance = apps.get_model("authentik_blueprints", "BlueprintInstance")
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
blueprints_discover()
|
||||
for blueprint in BlueprintInstance.objects.using(db_alias).all():
|
||||
# If we already have flows (and we should always run before flow migrations)
|
||||
# then this is an existing install and we want to disable all blueprints
|
||||
if Flow.objects.using(db_alias).all().exists():
|
||||
blueprint.enabled = False
|
||||
# System blueprints are always enabled
|
||||
if "/system/" in blueprint.path:
|
||||
blueprint.enabled = True
|
||||
blueprint.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
dependencies = [("authentik_flows", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
|
@ -38,6 +59,7 @@ class Migration(migrations.Migration):
|
|||
("path", models.TextField()),
|
||||
("context", models.JSONField()),
|
||||
("last_applied", models.DateTimeField(auto_now=True)),
|
||||
("last_applied_hash", models.TextField()),
|
||||
(
|
||||
"status",
|
||||
models.TextField(
|
||||
|
@ -45,6 +67,7 @@ class Migration(migrations.Migration):
|
|||
("successful", "Successful"),
|
||||
("warning", "Warning"),
|
||||
("error", "Error"),
|
||||
("orphaned", "Orphaned"),
|
||||
("unknown", "Unknown"),
|
||||
]
|
||||
),
|
||||
|
@ -63,4 +86,5 @@ class Migration(migrations.Migration):
|
|||
"unique_together": {("name", "path")},
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migration_blueprint_import),
|
||||
]
|
||||
|
|
|
@ -38,6 +38,7 @@ class BlueprintInstanceStatus(models.TextChoices):
|
|||
SUCCESSFUL = "successful"
|
||||
WARNING = "warning"
|
||||
ERROR = "error"
|
||||
ORPHANED = "orphaned"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
|
@ -51,6 +52,7 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
|||
path = models.TextField()
|
||||
context = models.JSONField()
|
||||
last_applied = models.DateTimeField(auto_now=True)
|
||||
last_applied_hash = models.TextField()
|
||||
status = models.TextField(choices=BlueprintInstanceStatus.choices)
|
||||
enabled = models.BooleanField(default=True)
|
||||
managed_models = ArrayField(models.TextField())
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
"""managed Settings"""
|
||||
"""blueprint Settings"""
|
||||
from celery.schedules import crontab
|
||||
|
||||
from authentik.lib.utils.time import fqdn_rand
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"blueprints_reconcile": {
|
||||
"task": "authentik.blueprints.tasks.managed_reconcile",
|
||||
"schedule": crontab(minute=fqdn_rand("managed_reconcile"), hour="*/4"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
"blueprints_config_file_discovery": {
|
||||
"task": "authentik.blueprints.tasks.config_file_discovery",
|
||||
"schedule": crontab(minute=fqdn_rand("config_file_discovery"), hour="*"),
|
||||
"blueprints_v1_discover": {
|
||||
"task": "authentik.blueprints.v1.tasks.blueprints_discover",
|
||||
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
"""managed tasks"""
|
||||
from django.db import DatabaseError
|
||||
from django.db.utils import ProgrammingError
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.core.tasks import CELERY_APP
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
|
||||
|
||||
@CELERY_APP.task(
|
||||
bind=True,
|
||||
base=MonitoredTask,
|
||||
retry_backoff=True,
|
||||
)
|
||||
@prefill_task
|
||||
def managed_reconcile(self: MonitoredTask):
|
||||
"""Run ObjectManager to ensure objects are up-to-date"""
|
||||
try:
|
||||
ObjectManager().run()
|
||||
self.set_status(
|
||||
TaskResult(TaskResultStatus.SUCCESSFUL, ["Successfully updated managed models."])
|
||||
)
|
||||
except (DatabaseError, ProgrammingError) as exc: # pragma: no cover
|
||||
self.set_status(TaskResult(TaskResultStatus.WARNING, [str(exc)]))
|
30
authentik/blueprints/tests/test_bundled.py
Normal file
30
authentik/blueprints/tests/test_bundled.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""test packaged blueprints"""
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from django.test import TransactionTestCase
|
||||
from django.utils.text import slugify
|
||||
|
||||
from authentik.blueprints.v1.importer import Importer
|
||||
|
||||
|
||||
class TestBundled(TransactionTestCase):
|
||||
"""Empty class, test methods are added dynamically"""
|
||||
|
||||
|
||||
def blueprint_tester(file_name: str) -> Callable:
|
||||
"""This is used instead of subTest for better visibility"""
|
||||
|
||||
def tester(self: TestBundled):
|
||||
with open(file_name, "r", encoding="utf8") as flow_yaml:
|
||||
importer = Importer(flow_yaml.read())
|
||||
self.assertTrue(importer.validate()[0])
|
||||
self.assertTrue(importer.apply())
|
||||
|
||||
return tester
|
||||
|
||||
|
||||
for flow_file in glob("blueprints/**/*.yaml", recursive=True):
|
||||
method_name = slugify(Path(flow_file).stem).replace("-", "_").replace(".", "_")
|
||||
setattr(TestBundled, f"test_flow_{method_name}", blueprint_tester(flow_file))
|
|
@ -1,13 +0,0 @@
|
|||
"""managed tests"""
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.blueprints.tasks import managed_reconcile
|
||||
|
||||
|
||||
class TestManaged(TestCase):
|
||||
"""managed tests"""
|
||||
|
||||
def test_reconcile(self):
|
||||
"""Test reconcile"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
managed_reconcile() # pylint: disable=no-value-for-parameter
|
|
@ -37,14 +37,14 @@ class TestFlowTransport(TransactionTestCase):
|
|||
def test_bundle_invalid_format(self):
|
||||
"""Test bundle with invalid format"""
|
||||
importer = Importer('{"version": 3}')
|
||||
self.assertFalse(importer.validate())
|
||||
self.assertFalse(importer.validate()[0])
|
||||
importer = Importer(
|
||||
(
|
||||
'{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
|
||||
'"model": "authentik_core.User"}]}'
|
||||
)
|
||||
)
|
||||
self.assertFalse(importer.validate())
|
||||
self.assertFalse(importer.validate()[0])
|
||||
|
||||
def test_export_validate_import(self):
|
||||
"""Test export and validate it"""
|
||||
|
@ -70,7 +70,7 @@ class TestFlowTransport(TransactionTestCase):
|
|||
export_yaml = exporter.export_to_string()
|
||||
|
||||
importer = Importer(export_yaml)
|
||||
self.assertTrue(importer.validate())
|
||||
self.assertTrue(importer.validate()[0])
|
||||
self.assertTrue(importer.apply())
|
||||
|
||||
self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
|
||||
|
@ -80,7 +80,7 @@ class TestFlowTransport(TransactionTestCase):
|
|||
count_initial = Prompt.objects.filter(field_key="username").count()
|
||||
|
||||
importer = Importer(STATIC_PROMPT_EXPORT)
|
||||
self.assertTrue(importer.validate())
|
||||
self.assertTrue(importer.validate()[0])
|
||||
self.assertTrue(importer.apply())
|
||||
|
||||
count_before = Prompt.objects.filter(field_key="username").count()
|
||||
|
@ -116,7 +116,7 @@ class TestFlowTransport(TransactionTestCase):
|
|||
export_yaml = exporter.export_to_string()
|
||||
|
||||
importer = Importer(export_yaml)
|
||||
self.assertTrue(importer.validate())
|
||||
self.assertTrue(importer.validate()[0])
|
||||
self.assertTrue(importer.apply())
|
||||
self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
|
||||
self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
|
||||
|
@ -160,5 +160,5 @@ class TestFlowTransport(TransactionTestCase):
|
|||
|
||||
importer = Importer(export_yaml)
|
||||
|
||||
self.assertTrue(importer.validate())
|
||||
self.assertTrue(importer.validate()[0])
|
||||
self.assertTrue(importer.apply())
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
"""test example flows in docs"""
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
from authentik.blueprints.v1.importer import Importer
|
||||
|
||||
|
||||
class TestTransportDocs(TransactionTestCase):
|
||||
"""Empty class, test methods are added dynamically"""
|
||||
|
||||
|
||||
def pbflow_tester(file_name: str) -> Callable:
|
||||
"""This is used instead of subTest for better visibility"""
|
||||
|
||||
def tester(self: TestTransportDocs):
|
||||
with open(file_name, "r", encoding="utf8") as flow_json:
|
||||
importer = Importer(flow_json.read())
|
||||
self.assertTrue(importer.validate())
|
||||
self.assertTrue(importer.apply())
|
||||
|
||||
return tester
|
||||
|
||||
|
||||
for flow_file in glob("website/static/flows/*.yaml"):
|
||||
method_name = Path(flow_file).stem.replace("-", "_").replace(".", "_")
|
||||
setattr(TestTransportDocs, f"test_flow_{method_name}", pbflow_tester(flow_file))
|
|
@ -13,6 +13,8 @@ from django.db.utils import IntegrityError
|
|||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.serializers import BaseSerializer, Serializer
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
from structlog.testing import capture_logs
|
||||
from structlog.types import EventDict
|
||||
from yaml import load
|
||||
|
||||
from authentik.blueprints.v1.common import (
|
||||
|
@ -198,17 +200,20 @@ class Importer:
|
|||
self.logger.debug("updated model", model=model, pk=model.pk)
|
||||
return True
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate loaded flow export, ensure all models are allowed
|
||||
def validate(self) -> tuple[bool, list[EventDict]]:
|
||||
"""Validate loaded blueprint export, ensure all models are allowed
|
||||
and serializers have no errors"""
|
||||
self.logger.debug("Starting flow import validation")
|
||||
self.logger.debug("Starting blueprint import validation")
|
||||
orig_import = deepcopy(self.__import)
|
||||
if self.__import.version != 1:
|
||||
self.logger.warning("Invalid bundle version")
|
||||
return False
|
||||
with transaction_rollback():
|
||||
return False, []
|
||||
with (
|
||||
transaction_rollback(),
|
||||
capture_logs() as logs,
|
||||
):
|
||||
successful = self._apply_models()
|
||||
if not successful:
|
||||
self.logger.debug("Flow validation failed")
|
||||
self.logger.debug("blueprint validation failed")
|
||||
self.__import = orig_import
|
||||
return successful
|
||||
return successful, logs
|
||||
|
|
84
authentik/blueprints/v1/tasks.py
Normal file
84
authentik/blueprints/v1/tasks.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
"""v1 blueprints tasks"""
|
||||
from glob import glob
|
||||
from hashlib import sha512
|
||||
from pathlib import Path
|
||||
|
||||
from django.db import DatabaseError, InternalError, ProgrammingError
|
||||
from yaml import load
|
||||
|
||||
from authentik.blueprints.models import BlueprintInstance, BlueprintInstanceStatus
|
||||
from authentik.blueprints.v1.common import BlueprintLoader
|
||||
from authentik.blueprints.v1.importer import Importer
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
@prefill_task
|
||||
def blueprints_discover():
|
||||
"""Find blueprints and check if they need to be created in the database"""
|
||||
for folder in CONFIG.y("blueprint_locations"):
|
||||
for file in glob(f"{folder}/**/*.yaml", recursive=True):
|
||||
check_blueprint_v1_file(Path(file))
|
||||
|
||||
|
||||
def check_blueprint_v1_file(path: Path):
|
||||
"""Check if blueprint should be imported"""
|
||||
with open(path, "r", encoding="utf-8") as blueprint_file:
|
||||
raw_blueprint = load(blueprint_file.read(), BlueprintLoader)
|
||||
version = raw_blueprint.get("version", 1)
|
||||
if version != 1:
|
||||
return
|
||||
blueprint_file.seek(0)
|
||||
file_hash = sha512(path.read_bytes()).hexdigest()
|
||||
instance: BlueprintInstance = BlueprintInstance.objects.filter(path=path).first()
|
||||
if not instance:
|
||||
instance = BlueprintInstance(
|
||||
name=path.name,
|
||||
path=str(path),
|
||||
context={},
|
||||
status=BlueprintInstanceStatus.UNKNOWN,
|
||||
enabled=True,
|
||||
managed_models=[],
|
||||
)
|
||||
instance.save()
|
||||
if instance.last_applied_hash != file_hash:
|
||||
apply_blueprint.delay(instance.pk.hex)
|
||||
instance.last_applied_hash = file_hash
|
||||
instance.save()
|
||||
|
||||
|
||||
@CELERY_APP.task(
|
||||
bind=True,
|
||||
base=MonitoredTask,
|
||||
)
|
||||
def apply_blueprint(self: MonitoredTask, instance_pk: str):
|
||||
"""Apply single blueprint"""
|
||||
self.save_on_success = False
|
||||
try:
|
||||
instance: BlueprintInstance = BlueprintInstance.objects.filter(pk=instance_pk).first()
|
||||
if not instance:
|
||||
return
|
||||
with open(instance.path, "r", encoding="utf-8") as blueprint_file:
|
||||
importer = Importer(blueprint_file.read())
|
||||
valid, logs = importer.validate()
|
||||
if not valid:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
instance.save()
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR, [x["event"] for x in logs]))
|
||||
return
|
||||
applied = importer.apply()
|
||||
if not applied:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
instance.save()
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR, "Failed to apply"))
|
||||
except (DatabaseError, ProgrammingError, InternalError) as exc:
|
||||
instance.status = BlueprintInstanceStatus.ERROR
|
||||
instance.save()
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
|
@ -1,22 +1,37 @@
|
|||
"""authentik core app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
class AuthentikCoreConfig(AppConfig):
|
||||
|
||||
class AuthentikCoreConfig(ManagedAppConfig):
|
||||
"""authentik core app config"""
|
||||
|
||||
name = "authentik.core"
|
||||
label = "authentik_core"
|
||||
verbose_name = "authentik Core"
|
||||
mountpoint = ""
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.core.signals")
|
||||
import_module("authentik.core.managed")
|
||||
def reconcile_load_core_signals(self):
|
||||
"""Load core signals"""
|
||||
self.import_module("authentik.core.signals")
|
||||
|
||||
def reconcile_debug_worker_hook(self):
|
||||
"""Dispatch startup tasks inline when debugging"""
|
||||
if settings.DEBUG:
|
||||
from authentik.root.celery import worker_ready_hook
|
||||
|
||||
worker_ready_hook()
|
||||
|
||||
def reconcile_source_inbuilt(self):
|
||||
"""Reconcile inbuilt source"""
|
||||
from authentik.core.models import Source
|
||||
|
||||
Source.objects.update_or_create(
|
||||
defaults={
|
||||
"name": "authentik Built-in",
|
||||
"slug": "authentik-built-in",
|
||||
},
|
||||
managed="goauthentik.io/sources/inbuilt",
|
||||
)
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
"""Core managed objects"""
|
||||
from authentik.blueprints.manager import EnsureExists, ObjectManager
|
||||
from authentik.core.models import Source
|
||||
|
||||
|
||||
class CoreManager(ObjectManager):
|
||||
"""Core managed objects"""
|
||||
|
||||
def reconcile(self):
|
||||
return [
|
||||
EnsureExists(
|
||||
Source,
|
||||
"goauthentik.io/sources/inbuilt",
|
||||
name="authentik Built-in",
|
||||
slug="authentik-built-in",
|
||||
),
|
||||
]
|
|
@ -22,8 +22,8 @@ from structlog.stdlib import get_logger
|
|||
from authentik.api.decorators import permission_required
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.crypto.apps import MANAGED_KEY
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.managed import MANAGED_KEY
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
|
|
|
@ -1,16 +1,55 @@
|
|||
"""authentik crypto app config"""
|
||||
from importlib import import_module
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from django.apps import AppConfig
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
MANAGED_KEY = "goauthentik.io/crypto/jwt-managed"
|
||||
|
||||
|
||||
class AuthentikCryptoConfig(AppConfig):
|
||||
class AuthentikCryptoConfig(ManagedAppConfig):
|
||||
"""authentik crypto app config"""
|
||||
|
||||
name = "authentik.crypto"
|
||||
label = "authentik_crypto"
|
||||
verbose_name = "authentik Crypto"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.crypto.managed")
|
||||
import_module("authentik.crypto.tasks")
|
||||
def reconcile_load_crypto_tasks(self):
|
||||
"""Load crypto tasks"""
|
||||
self.import_module("authentik.crypto.tasks")
|
||||
|
||||
def _create_update_cert(self, cert: Optional["CertificateKeyPair"] = None):
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
builder = CertificateBuilder()
|
||||
builder.common_name = "goauthentik.io"
|
||||
builder.build(
|
||||
subject_alt_names=["goauthentik.io"],
|
||||
validity_days=360,
|
||||
)
|
||||
if not cert:
|
||||
|
||||
cert = CertificateKeyPair()
|
||||
cert.certificate_data = builder.certificate
|
||||
cert.key_data = builder.private_key
|
||||
cert.name = "authentik Internal JWT Certificate"
|
||||
cert.managed = MANAGED_KEY
|
||||
cert.save()
|
||||
|
||||
def reconcile_managed_jwt_cert(self):
|
||||
"""Ensure managed JWT certificate"""
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
certs = CertificateKeyPair.objects.filter(managed=MANAGED_KEY)
|
||||
if not certs.exists():
|
||||
self._create_update_cert()
|
||||
return
|
||||
cert: CertificateKeyPair = certs.first()
|
||||
now = datetime.now()
|
||||
if now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after:
|
||||
self._create_update_cert(cert)
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
"""Crypto managed objects"""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
MANAGED_KEY = "goauthentik.io/crypto/jwt-managed"
|
||||
|
||||
|
||||
class CryptoManager(ObjectManager):
|
||||
"""Crypto managed objects"""
|
||||
|
||||
def _create(self, cert: Optional[CertificateKeyPair] = None):
|
||||
builder = CertificateBuilder()
|
||||
builder.common_name = "goauthentik.io"
|
||||
builder.build(
|
||||
subject_alt_names=["goauthentik.io"],
|
||||
validity_days=360,
|
||||
)
|
||||
if not cert:
|
||||
cert = CertificateKeyPair()
|
||||
cert.certificate_data = builder.certificate
|
||||
cert.key_data = builder.private_key
|
||||
cert.name = "authentik Internal JWT Certificate"
|
||||
cert.managed = MANAGED_KEY
|
||||
cert.save()
|
||||
|
||||
def reconcile(self):
|
||||
certs = CertificateKeyPair.objects.filter(managed=MANAGED_KEY)
|
||||
if not certs.exists():
|
||||
self._create()
|
||||
return []
|
||||
cert: CertificateKeyPair = certs.first()
|
||||
now = datetime.now()
|
||||
if now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after:
|
||||
self._create(cert)
|
||||
return []
|
||||
return []
|
|
@ -1,9 +1,8 @@
|
|||
"""authentik events app"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from prometheus_client import Gauge
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
GAUGE_TASKS = Gauge(
|
||||
"authentik_system_tasks",
|
||||
"System tasks and their status",
|
||||
|
@ -11,12 +10,14 @@ GAUGE_TASKS = Gauge(
|
|||
)
|
||||
|
||||
|
||||
class AuthentikEventsConfig(AppConfig):
|
||||
class AuthentikEventsConfig(ManagedAppConfig):
|
||||
"""authentik events app"""
|
||||
|
||||
name = "authentik.events"
|
||||
label = "authentik_events"
|
||||
verbose_name = "authentik Events"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.events.signals")
|
||||
def reconcile_load_events_signals(self):
|
||||
"""Load events signals"""
|
||||
self.import_module("authentik.events.signals")
|
||||
|
|
|
@ -168,7 +168,8 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
|
|||
if not file:
|
||||
return HttpResponseBadRequest()
|
||||
importer = Importer(file.read().decode())
|
||||
valid = importer.validate()
|
||||
valid, _logs = importer.validate()
|
||||
# TODO: return logs
|
||||
if not valid:
|
||||
return HttpResponseBadRequest()
|
||||
successful = importer.apply()
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
"""authentik flows app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.utils import ProgrammingError
|
||||
from prometheus_client import Gauge, Histogram
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
from authentik.lib.utils.reflection import all_subclasses
|
||||
|
||||
GAUGE_FLOWS_CACHED = Gauge(
|
||||
|
@ -18,20 +15,22 @@ HIST_FLOWS_PLAN_TIME = Histogram(
|
|||
)
|
||||
|
||||
|
||||
class AuthentikFlowsConfig(AppConfig):
|
||||
class AuthentikFlowsConfig(ManagedAppConfig):
|
||||
"""authentik flows app config"""
|
||||
|
||||
name = "authentik.flows"
|
||||
label = "authentik_flows"
|
||||
mountpoint = "flows/"
|
||||
verbose_name = "authentik Flows"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.flows.signals")
|
||||
try:
|
||||
from authentik.flows.models import Stage
|
||||
def reconcile_load_flows_signals(self):
|
||||
"""Load flows signals"""
|
||||
self.import_module("authentik.flows.signals")
|
||||
|
||||
for stage in all_subclasses(Stage):
|
||||
_ = stage().type
|
||||
except ProgrammingError:
|
||||
pass
|
||||
def reconcile_stages_loaded(self):
|
||||
"""Ensure all stages are loaded"""
|
||||
from authentik.flows.models import Stage
|
||||
|
||||
for stage in all_subclasses(Stage):
|
||||
_ = stage().type
|
||||
|
|
|
@ -62,7 +62,6 @@ ldap:
|
|||
tls:
|
||||
ciphers: null
|
||||
|
||||
config_file_dir: "/config"
|
||||
cookie_domain: null
|
||||
disable_update_check: false
|
||||
disable_startup_analytics: false
|
||||
|
@ -79,3 +78,6 @@ gdpr_compliance: true
|
|||
cert_discovery_dir: /certs
|
||||
default_token_length: 128
|
||||
impersonation: true
|
||||
|
||||
blueprint_locations:
|
||||
- /blueprints
|
||||
|
|
|
@ -18,7 +18,7 @@ from authentik.core.api.used_by import UsedByMixin
|
|||
from authentik.core.api.utils import PassiveSerializer, is_dict
|
||||
from authentik.core.models import Provider
|
||||
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType, default_outpost_config
|
||||
from authentik.providers.ldap.models import LDAPProvider
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
"""authentik outposts app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from prometheus_client import Gauge
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
GAUGE_OUTPOSTS_CONNECTED = Gauge(
|
||||
|
@ -15,15 +14,47 @@ GAUGE_OUTPOSTS_LAST_UPDATE = Gauge(
|
|||
"Last update from any outpost",
|
||||
["outpost", "uid", "version"],
|
||||
)
|
||||
MANAGED_OUTPOST = "goauthentik.io/outposts/embedded"
|
||||
|
||||
|
||||
class AuthentikOutpostConfig(AppConfig):
|
||||
class AuthentikOutpostConfig(ManagedAppConfig):
|
||||
"""authentik outposts app config"""
|
||||
|
||||
name = "authentik.outposts"
|
||||
label = "authentik_outposts"
|
||||
verbose_name = "authentik Outpost"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.outposts.signals")
|
||||
import_module("authentik.outposts.managed")
|
||||
def reconcile_load_outposts_signals(self):
|
||||
"""Load outposts signals"""
|
||||
self.import_module("authentik.outposts.signals")
|
||||
|
||||
def reconcile_embedded_outpost(self):
|
||||
"""Ensure embedded outpost"""
|
||||
from authentik.outposts.models import (
|
||||
DockerServiceConnection,
|
||||
KubernetesServiceConnection,
|
||||
Outpost,
|
||||
OutpostConfig,
|
||||
OutpostType,
|
||||
)
|
||||
|
||||
outpost, updated = Outpost.objects.update_or_create(
|
||||
defaults={
|
||||
"name": "authentik Embedded Outpost",
|
||||
"type": OutpostType.PROXY,
|
||||
},
|
||||
managed=MANAGED_OUTPOST,
|
||||
)
|
||||
if updated:
|
||||
if KubernetesServiceConnection.objects.exists():
|
||||
outpost.service_connection = KubernetesServiceConnection.objects.first()
|
||||
elif DockerServiceConnection.objects.exists():
|
||||
outpost.service_connection = DockerServiceConnection.objects.first()
|
||||
outpost.config = OutpostConfig(
|
||||
kubernetes_disabled_components=[
|
||||
"deployment",
|
||||
"secret",
|
||||
]
|
||||
)
|
||||
outpost.save()
|
||||
|
|
|
@ -14,10 +14,10 @@ from structlog.stdlib import get_logger
|
|||
from yaml import safe_dump
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
|
||||
from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException
|
||||
from authentik.outposts.docker_tls import DockerInlineTLS
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import (
|
||||
DockerServiceConnection,
|
||||
Outpost,
|
||||
|
|
|
@ -10,8 +10,8 @@ from structlog.stdlib import get_logger
|
|||
from urllib3.exceptions import HTTPError
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||
|
|
|
@ -78,7 +78,7 @@ class DockerInlineSSH:
|
|||
"""Cleanup when we're done"""
|
||||
try:
|
||||
os.unlink(self.key_path)
|
||||
with open(self.config_path, "r+", encoding="utf-8") as ssh_config:
|
||||
with open(self.config_path, "r", encoding="utf-8") as ssh_config:
|
||||
start = 0
|
||||
end = 0
|
||||
lines = ssh_config.readlines()
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
"""Outpost managed objects"""
|
||||
from authentik.blueprints.manager import EnsureExists, ObjectManager
|
||||
from authentik.outposts.models import (
|
||||
DockerServiceConnection,
|
||||
KubernetesServiceConnection,
|
||||
Outpost,
|
||||
OutpostConfig,
|
||||
OutpostType,
|
||||
)
|
||||
|
||||
MANAGED_OUTPOST = "goauthentik.io/outposts/embedded"
|
||||
|
||||
|
||||
class OutpostManager(ObjectManager):
|
||||
"""Outpost managed objects"""
|
||||
|
||||
def reconcile(self):
|
||||
def outpost_created(outpost: Outpost):
|
||||
"""When outpost is initially created, and we already have a service connection,
|
||||
auto-assign it."""
|
||||
if KubernetesServiceConnection.objects.exists():
|
||||
outpost.service_connection = KubernetesServiceConnection.objects.first()
|
||||
elif DockerServiceConnection.objects.exists():
|
||||
outpost.service_connection = DockerServiceConnection.objects.first()
|
||||
outpost.config = OutpostConfig(
|
||||
kubernetes_disabled_components=[
|
||||
"deployment",
|
||||
"secret",
|
||||
]
|
||||
)
|
||||
outpost.save()
|
||||
|
||||
return [
|
||||
EnsureExists(
|
||||
Outpost,
|
||||
MANAGED_OUTPOST,
|
||||
created_callback=outpost_created,
|
||||
name="authentik Embedded Outpost",
|
||||
type=OutpostType.PROXY,
|
||||
),
|
||||
]
|
|
@ -233,7 +233,7 @@ def _outpost_single_update(outpost: Outpost, layer=None):
|
|||
def outpost_local_connection():
|
||||
"""Checks the local environment and create Service connections."""
|
||||
if not CONFIG.y_bool("outposts.discover"):
|
||||
LOGGER.debug("outpost integration discovery is disabled")
|
||||
LOGGER.debug("Outpost integration discovery is disabled")
|
||||
return
|
||||
# Explicitly check against token filename, as that's
|
||||
# only present when the integration is enabled
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
"""Docker controller tests"""
|
||||
from django.apps import apps
|
||||
from django.test import TestCase
|
||||
from docker.models.containers import Container
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.controllers.base import ControllerException
|
||||
from authentik.outposts.controllers.docker import DockerController
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
|
||||
from authentik.providers.proxy.controllers.docker import ProxyDockerController
|
||||
|
||||
|
@ -19,7 +19,7 @@ class DockerControllerTests(TestCase):
|
|||
type=OutpostType.PROXY,
|
||||
)
|
||||
self.integration = DockerServiceConnection(name="test")
|
||||
ObjectManager().run()
|
||||
apps.get_app_config("authentik_outposts").reconcile()
|
||||
|
||||
def test_init_managed(self):
|
||||
"""Docker controller shouldn't do anything for managed outpost"""
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
"""authentik policies app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from prometheus_client import Gauge, Histogram
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
GAUGE_POLICIES_CACHED = Gauge(
|
||||
"authentik_policies_cached",
|
||||
"Cached Policies",
|
||||
|
@ -27,12 +26,14 @@ HIST_POLICIES_EXECUTION_TIME = Histogram(
|
|||
)
|
||||
|
||||
|
||||
class AuthentikPoliciesConfig(AppConfig):
|
||||
class AuthentikPoliciesConfig(ManagedAppConfig):
|
||||
"""authentik policies app config"""
|
||||
|
||||
name = "authentik.policies"
|
||||
label = "authentik_policies"
|
||||
verbose_name = "authentik Policies"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.policies.signals")
|
||||
def reconcile_load_policies_signals(self):
|
||||
"""Load policies signals"""
|
||||
self.import_module("authentik.policies.signals")
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
"""Authentik reputation_policy app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
|
||||
class AuthentikPolicyReputationConfig(AppConfig):
|
||||
class AuthentikPolicyReputationConfig(ManagedAppConfig):
|
||||
"""Authentik reputation app config"""
|
||||
|
||||
name = "authentik.policies.reputation"
|
||||
label = "authentik_policies_reputation"
|
||||
verbose_name = "authentik Policies.Reputation"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.policies.reputation.signals")
|
||||
import_module("authentik.policies.reputation.tasks")
|
||||
def reconcile_load_policies_reputation_signals(self):
|
||||
"""Load policies.reputation signals"""
|
||||
self.import_module("authentik.policies.reputation.signals")
|
||||
|
||||
def reconcile_load_policies_reputation_tasks(self):
|
||||
"""Load policies.reputation tasks"""
|
||||
self.import_module("authentik.policies.reputation.tasks")
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
"""authentik oauth provider app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
@ -14,6 +12,3 @@ class AuthentikProviderOAuth2Config(AppConfig):
|
|||
"authentik.providers.oauth2.urls_github": "",
|
||||
"authentik.providers.oauth2.urls": "application/o/",
|
||||
}
|
||||
|
||||
def ready(self) -> None:
|
||||
import_module("authentik.providers.oauth2.managed")
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
"""OAuth2 Provider managed objects"""
|
||||
from authentik.blueprints.manager import EnsureExists, ObjectManager
|
||||
from authentik.providers.oauth2.models import ScopeMapping
|
||||
|
||||
SCOPE_OPENID_EXPRESSION = """
|
||||
# This scope is required by the OpenID-spec, and must as such exist in authentik.
|
||||
# The scope by itself does not grant any information
|
||||
return {}
|
||||
"""
|
||||
SCOPE_EMAIL_EXPRESSION = """
|
||||
return {
|
||||
"email": request.user.email,
|
||||
"email_verified": True
|
||||
}
|
||||
"""
|
||||
SCOPE_PROFILE_EXPRESSION = """
|
||||
return {
|
||||
# Because authentik only saves the user's full name, and has no concept of first and last names,
|
||||
# the full name is used as given name.
|
||||
# You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
|
||||
"name": request.user.name,
|
||||
"given_name": request.user.name,
|
||||
"family_name": "",
|
||||
"preferred_username": request.user.username,
|
||||
"nickname": request.user.username,
|
||||
# groups is not part of the official userinfo schema, but is a quasi-standard
|
||||
"groups": [group.name for group in request.user.ak_groups.all()],
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class ScopeMappingManager(ObjectManager):
|
||||
"""OAuth2 Provider managed objects"""
|
||||
|
||||
def reconcile(self):
|
||||
return [
|
||||
EnsureExists(
|
||||
ScopeMapping,
|
||||
"goauthentik.io/providers/oauth2/scope-openid",
|
||||
name="authentik default OAuth Mapping: OpenID 'openid'",
|
||||
scope_name="openid",
|
||||
expression=SCOPE_OPENID_EXPRESSION,
|
||||
),
|
||||
EnsureExists(
|
||||
ScopeMapping,
|
||||
"goauthentik.io/providers/oauth2/scope-email",
|
||||
name="authentik default OAuth Mapping: OpenID 'email'",
|
||||
scope_name="email",
|
||||
description="Email address",
|
||||
expression=SCOPE_EMAIL_EXPRESSION,
|
||||
),
|
||||
EnsureExists(
|
||||
ScopeMapping,
|
||||
"goauthentik.io/providers/oauth2/scope-profile",
|
||||
name="authentik default OAuth Mapping: OpenID 'profile'",
|
||||
scope_name="profile",
|
||||
description="General Profile Information",
|
||||
expression=SCOPE_PROFILE_EXPRESSION,
|
||||
),
|
||||
]
|
|
@ -5,7 +5,7 @@ from django.test import RequestFactory
|
|||
from django.urls import reverse
|
||||
from jwt import decode
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import USER_ATTRIBUTE_SA, Application, Group, Token, TokenIntents
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
|
@ -24,9 +24,9 @@ from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
|||
class TestTokenClientCredentials(OAuthTestCase):
|
||||
"""Test token (client_credentials) view"""
|
||||
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
ObjectManager().run()
|
||||
self.factory = RequestFactory()
|
||||
self.provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.test import RequestFactory
|
|||
from django.urls import reverse
|
||||
from jwt import decode
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application, Group
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
|
@ -26,9 +26,9 @@ from authentik.sources.oauth.models import OAuthSource
|
|||
class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||
"""Test token (client_credentials, with JWT) view"""
|
||||
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
ObjectManager().run()
|
||||
self.factory = RequestFactory()
|
||||
self.cert = create_test_cert()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from dataclasses import asdict
|
|||
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
@ -16,9 +16,9 @@ from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
|||
class TestUserinfo(OAuthTestCase):
|
||||
"""Test token view"""
|
||||
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
ObjectManager().run()
|
||||
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
|
||||
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
|
||||
name=generate_id(),
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
"""authentik Proxy app"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
@ -10,6 +8,3 @@ class AuthentikProviderProxyConfig(AppConfig):
|
|||
name = "authentik.providers.proxy"
|
||||
label = "authentik_providers_proxy"
|
||||
verbose_name = "authentik Providers.Proxy"
|
||||
|
||||
def ready(self) -> None:
|
||||
import_module("authentik.providers.proxy.managed")
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
"""OAuth2 Provider managed objects"""
|
||||
from authentik.blueprints.manager import EnsureExists, ObjectManager
|
||||
from authentik.providers.oauth2.models import ScopeMapping
|
||||
from authentik.providers.proxy.models import SCOPE_AK_PROXY
|
||||
|
||||
SCOPE_AK_PROXY_EXPRESSION = """
|
||||
# This mapping is used by the authentik proxy. It passes extra user attributes,
|
||||
# which are used for example for the HTTP-Basic Authentication mapping.
|
||||
return {
|
||||
"ak_proxy": {
|
||||
"user_attributes": request.user.group_attributes(request),
|
||||
"is_superuser": request.user.is_superuser,
|
||||
}
|
||||
}"""
|
||||
|
||||
|
||||
class ProxyScopeMappingManager(ObjectManager):
|
||||
"""OAuth2 Provider managed objects"""
|
||||
|
||||
def reconcile(self):
|
||||
return [
|
||||
EnsureExists(
|
||||
ScopeMapping,
|
||||
"goauthentik.io/providers/proxy/scope-proxy",
|
||||
name="authentik default OAuth Mapping: Proxy outpost",
|
||||
scope_name=SCOPE_AK_PROXY,
|
||||
expression=SCOPE_AK_PROXY_EXPRESSION,
|
||||
),
|
||||
]
|
|
@ -1,5 +1,4 @@
|
|||
"""authentik SAML IdP app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
@ -11,6 +10,3 @@ class AuthentikProviderSAMLConfig(AppConfig):
|
|||
label = "authentik_providers_saml"
|
||||
verbose_name = "authentik Providers.SAML"
|
||||
mountpoint = "application/saml/"
|
||||
|
||||
def ready(self) -> None:
|
||||
import_module("authentik.providers.saml.managed")
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
"""SAML Provider managed objects"""
|
||||
from authentik.blueprints.manager import EnsureExists, ObjectManager
|
||||
from authentik.providers.saml.models import SAMLPropertyMapping
|
||||
|
||||
GROUP_EXPRESSION = """
|
||||
for group in request.user.ak_groups.all():
|
||||
yield group.name
|
||||
"""
|
||||
|
||||
|
||||
class SAMLProviderManager(ObjectManager):
|
||||
"""SAML Provider managed objects"""
|
||||
|
||||
def reconcile(self):
|
||||
return [
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/upn",
|
||||
name="authentik default SAML Mapping: UPN",
|
||||
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
|
||||
expression="return request.user.attributes.get('upn', request.user.email)",
|
||||
friendly_name="",
|
||||
),
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/name",
|
||||
name="authentik default SAML Mapping: Name",
|
||||
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
|
||||
expression="return request.user.name",
|
||||
friendly_name="",
|
||||
),
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/email",
|
||||
name="authentik default SAML Mapping: Email",
|
||||
saml_name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
|
||||
expression="return request.user.email",
|
||||
friendly_name="",
|
||||
),
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/username",
|
||||
name="authentik default SAML Mapping: Username",
|
||||
saml_name="http://schemas.goauthentik.io/2021/02/saml/username",
|
||||
expression="return request.user.username",
|
||||
friendly_name="",
|
||||
),
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/uid",
|
||||
name="authentik default SAML Mapping: User ID",
|
||||
saml_name="http://schemas.goauthentik.io/2021/02/saml/uid",
|
||||
expression="return request.user.pk",
|
||||
friendly_name="",
|
||||
),
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/groups",
|
||||
name="authentik default SAML Mapping: Groups",
|
||||
saml_name="http://schemas.xmlsoap.org/claims/Group",
|
||||
expression=GROUP_EXPRESSION,
|
||||
friendly_name="",
|
||||
),
|
||||
EnsureExists(
|
||||
SAMLPropertyMapping,
|
||||
"goauthentik.io/providers/saml/ms-windowsaccountname",
|
||||
name="authentik default SAML Mapping: WindowsAccountname (Username)",
|
||||
saml_name=(
|
||||
"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
|
||||
),
|
||||
expression="return request.user.username",
|
||||
friendly_name="",
|
||||
),
|
||||
]
|
|
@ -4,7 +4,7 @@ from base64 import b64encode
|
|||
from django.http.request import QueryDict
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
@ -74,8 +74,8 @@ qNAZMq1DqpibfCBg
|
|||
class TestAuthNRequest(TestCase):
|
||||
"""Test AuthN Request generator and parser"""
|
||||
|
||||
@apply_blueprint("blueprints/system/providers-saml.yaml")
|
||||
def setUp(self):
|
||||
ObjectManager().run()
|
||||
cert = create_test_cert()
|
||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
authorization_flow=create_test_flow(),
|
||||
|
|
|
@ -4,7 +4,7 @@ from base64 import b64encode
|
|||
from django.test import RequestFactory, TestCase
|
||||
from lxml import etree # nosec
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.lib.tests.utils import get_request
|
||||
from authentik.lib.xml import lxml_from_string
|
||||
|
@ -18,8 +18,8 @@ from authentik.sources.saml.processors.request import RequestProcessor
|
|||
class TestSchema(TestCase):
|
||||
"""Test Requests and Responses against schema"""
|
||||
|
||||
@apply_blueprint("blueprints/system/providers-saml.yaml")
|
||||
def setUp(self):
|
||||
ObjectManager().run()
|
||||
cert = create_test_cert()
|
||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||
authorization_flow=create_test_flow(),
|
||||
|
|
|
@ -58,6 +58,7 @@ def task_prerun_hook(task_id: str, task, *args, **kwargs):
|
|||
@task_postrun.connect
|
||||
def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
|
||||
"""Log task_id on worker"""
|
||||
CTX_TASK_ID.set(...)
|
||||
LOGGER.info("Task finished", task_id=task_id, task_name=task.__name__, state=state)
|
||||
|
||||
|
||||
|
@ -69,6 +70,7 @@ def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
|
|||
from authentik.events.models import Event, EventAction
|
||||
|
||||
LOGGER.warning("Task failure", exc=exception)
|
||||
CTX_TASK_ID.set(...)
|
||||
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
||||
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
|
||||
|
||||
|
@ -76,7 +78,6 @@ def task_error_hook(task_id, exception: Exception, traceback, *args, **kwargs):
|
|||
def _get_startup_tasks() -> list[Callable]:
|
||||
"""Get all tasks to be run on startup"""
|
||||
from authentik.admin.tasks import clear_update_notifications
|
||||
from authentik.blueprints.tasks import managed_reconcile
|
||||
from authentik.outposts.tasks import outpost_controller_all, outpost_local_connection
|
||||
from authentik.providers.proxy.tasks import proxy_set_defaults
|
||||
|
||||
|
@ -85,7 +86,6 @@ def _get_startup_tasks() -> list[Callable]:
|
|||
outpost_local_connection,
|
||||
outpost_controller_all,
|
||||
proxy_set_defaults,
|
||||
managed_reconcile,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
"""authentik ldap source config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
|
||||
class AuthentikSourceLDAPConfig(AppConfig):
|
||||
class AuthentikSourceLDAPConfig(ManagedAppConfig):
|
||||
"""Authentik ldap app config"""
|
||||
|
||||
name = "authentik.sources.ldap"
|
||||
label = "authentik_sources_ldap"
|
||||
verbose_name = "authentik Sources.LDAP"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.sources.ldap.signals")
|
||||
import_module("authentik.sources.ldap.managed")
|
||||
def reconcile_load_sources_ldap_signals(self):
|
||||
"""Load sources.ldap signals"""
|
||||
self.import_module("authentik.sources.ldap.signals")
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
"""LDAP Source managed objects"""
|
||||
from authentik.blueprints.manager import EnsureExists, ObjectManager
|
||||
from authentik.sources.ldap.models import LDAPPropertyMapping
|
||||
|
||||
|
||||
class LDAPProviderManager(ObjectManager):
|
||||
"""LDAP Source managed objects"""
|
||||
|
||||
def reconcile(self):
|
||||
return [
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/default-name",
|
||||
name="authentik default LDAP Mapping: Name",
|
||||
object_field="name",
|
||||
expression="return ldap.get('name')",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/default-mail",
|
||||
name="authentik default LDAP Mapping: mail",
|
||||
object_field="email",
|
||||
expression="return ldap.get('mail')",
|
||||
),
|
||||
# Active Directory-specific mappings
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/ms-samaccountname",
|
||||
name="authentik default Active Directory Mapping: sAMAccountName",
|
||||
object_field="username",
|
||||
expression="return ldap.get('sAMAccountName')",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/ms-userprincipalname",
|
||||
name="authentik default Active Directory Mapping: userPrincipalName",
|
||||
object_field="attributes.upn",
|
||||
expression="return list_flatten(ldap.get('userPrincipalName'))",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/ms-givenName",
|
||||
name="authentik default Active Directory Mapping: givenName",
|
||||
object_field="attributes.givenName",
|
||||
expression="return list_flatten(ldap.get('givenName'))",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/ms-sn",
|
||||
name="authentik default Active Directory Mapping: sn",
|
||||
object_field="attributes.sn",
|
||||
expression="return list_flatten(ldap.get('sn'))",
|
||||
),
|
||||
# OpenLDAP specific mappings
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/openldap-uid",
|
||||
name="authentik default OpenLDAP Mapping: uid",
|
||||
object_field="username",
|
||||
expression="return ldap.get('uid')",
|
||||
),
|
||||
EnsureExists(
|
||||
LDAPPropertyMapping,
|
||||
"goauthentik.io/sources/ldap/openldap-cn",
|
||||
name="authentik default OpenLDAP Mapping: cn",
|
||||
object_field="name",
|
||||
expression="return ldap.get('cn')",
|
||||
),
|
||||
]
|
|
@ -4,7 +4,7 @@ from unittest.mock import Mock, PropertyMock, patch
|
|||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import User
|
||||
from authentik.lib.generators import generate_key
|
||||
from authentik.sources.ldap.auth import LDAPBackend
|
||||
|
@ -19,8 +19,8 @@ LDAP_PASSWORD = generate_key()
|
|||
class LDAPSyncTests(TestCase):
|
||||
"""LDAP Sync tests"""
|
||||
|
||||
@apply_blueprint("blueprints/system/sources-ldap.yaml")
|
||||
def setUp(self):
|
||||
ObjectManager().run()
|
||||
self.source = LDAPSource.objects.create(
|
||||
name="ldap",
|
||||
slug="ldap",
|
||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import PropertyMock, patch
|
|||
from django.db.models import Q
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
@ -23,8 +23,8 @@ LDAP_PASSWORD = generate_key()
|
|||
class LDAPSyncTests(TestCase):
|
||||
"""LDAP Sync tests"""
|
||||
|
||||
@apply_blueprint("blueprints/system/sources-ldap.yaml")
|
||||
def setUp(self):
|
||||
ObjectManager().run()
|
||||
self.source: LDAPSource = LDAPSource.objects.create(
|
||||
name="ldap",
|
||||
slug="ldap",
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
"""authentik oauth_client config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
||||
|
@ -21,18 +20,19 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
|||
]
|
||||
|
||||
|
||||
class AuthentikSourceOAuthConfig(AppConfig):
|
||||
class AuthentikSourceOAuthConfig(ManagedAppConfig):
|
||||
"""authentik source.oauth config"""
|
||||
|
||||
name = "authentik.sources.oauth"
|
||||
label = "authentik_sources_oauth"
|
||||
verbose_name = "authentik Sources.OAuth"
|
||||
mountpoint = "source/oauth/"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
def reconcile_sources_loaded(self):
|
||||
"""Load source_types from config file"""
|
||||
for source_type in AUTHENTIK_SOURCES_OAUTH_TYPES:
|
||||
try:
|
||||
import_module(source_type)
|
||||
self.import_module(source_type)
|
||||
except ImportError as exc:
|
||||
LOGGER.warning("Failed to load OAuth Source", exc=exc)
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
"""Authentik SAML app config"""
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
|
||||
class AuthentikSourceSAMLConfig(AppConfig):
|
||||
class AuthentikSourceSAMLConfig(ManagedAppConfig):
|
||||
"""authentik saml source app config"""
|
||||
|
||||
name = "authentik.sources.saml"
|
||||
label = "authentik_sources_saml"
|
||||
verbose_name = "authentik Sources.SAML"
|
||||
mountpoint = "source/saml/"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.sources.saml.signals")
|
||||
def reconcile_load_sources_saml_signals(self):
|
||||
"""Load sources.saml signals"""
|
||||
self.import_module("authentik.sources.saml.signals")
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
"""Authenticator Static stage"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
|
||||
class AuthentikStageAuthenticatorStaticConfig(AppConfig):
|
||||
class AuthentikStageAuthenticatorStaticConfig(ManagedAppConfig):
|
||||
"""Authenticator Static stage"""
|
||||
|
||||
name = "authentik.stages.authenticator_static"
|
||||
label = "authentik_stages_authenticator_static"
|
||||
verbose_name = "authentik Stages.Authenticator.Static"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.stages.authenticator_static.signals")
|
||||
def reconcile_load_stages_authenticator_static_signals(self):
|
||||
"""Load stages.authenticator_static signals"""
|
||||
self.import_module("authentik.stages.authenticator_static.signals")
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
"""authentik email stage config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db import ProgrammingError
|
||||
from django.template.exceptions import TemplateDoesNotExist
|
||||
from django.template.loader import get_template
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class AuthentikStageEmailConfig(AppConfig):
|
||||
class AuthentikStageEmailConfig(ManagedAppConfig):
|
||||
"""authentik email stage config"""
|
||||
|
||||
name = "authentik.stages.email"
|
||||
label = "authentik_stages_email"
|
||||
verbose_name = "authentik Stages.Email"
|
||||
default = True
|
||||
|
||||
def ready(self):
|
||||
import_module("authentik.stages.email.tasks")
|
||||
try:
|
||||
self.validate_stage_templates()
|
||||
except ProgrammingError:
|
||||
pass
|
||||
def reconcile_load_stages_emails_tasks(self):
|
||||
"""Load stages.emails tasks"""
|
||||
self.import_module("authentik.stages.email.tasks")
|
||||
|
||||
def validate_stage_templates(self):
|
||||
def reconcile_stage_templates_valid(self):
|
||||
"""Ensure all stage's templates actually exist"""
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.stages.email.models import EmailStage, EmailTemplates
|
||||
|
|
|
@ -27,7 +27,7 @@ entries:
|
|||
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'', {})
|
||||
return 'username' not in context.get('prompt_data', {})
|
||||
meta_model_name: authentik_policies_expression.expressionpolicy
|
||||
identifiers:
|
||||
name: default-source-enrollment-if-username
|
||||
|
@ -78,7 +78,7 @@ entries:
|
|||
order: 0
|
||||
stage: !KeyOf default-source-enrollment-prompt
|
||||
target: !KeyOf flow
|
||||
id: prompt-binding
|
||||
id: prompt-binding
|
||||
model: authentik_flows.flowstagebinding
|
||||
- attrs:
|
||||
evaluate_on_plan: true
|
||||
|
|
|
@ -5,7 +5,7 @@ entries:
|
|||
layout: stacked
|
||||
name: Pre-Authentication
|
||||
policy_engine_mode: any
|
||||
title: ''
|
||||
title: Pre-authentication
|
||||
identifiers:
|
||||
slug: default-source-pre-authentication
|
||||
model: authentik_flows.flow
|
||||
|
|
|
@ -3,9 +3,9 @@ entries:
|
|||
compatibility_mode: false
|
||||
designation: stage_configuration
|
||||
layout: stacked
|
||||
name: Update your info
|
||||
name: User settings
|
||||
policy_engine_mode: any
|
||||
title: ''
|
||||
title: Update your info
|
||||
identifiers:
|
||||
slug: default-user-settings-flow
|
||||
model: authentik_flows.flow
|
||||
|
@ -108,9 +108,9 @@ entries:
|
|||
|
||||
return True
|
||||
meta_model_name: authentik_policies_expression.expressionpolicy
|
||||
name: default-user-settings-authorization
|
||||
identifiers:
|
||||
name: default-user-settings-authorization
|
||||
id: default-user-settings-authorization
|
||||
model: authentik_policies_expression.expressionpolicy
|
||||
- attrs:
|
||||
create_users_as_inactive: false
|
||||
|
|
|
@ -76,7 +76,6 @@ entries:
|
|||
- !KeyOf prompt-field-password
|
||||
- !KeyOf prompt-field-password-repeat
|
||||
- identifiers:
|
||||
pk: !KeyOf default-enrollment-user-login
|
||||
name: default-enrollment-user-login
|
||||
id: default-enrollment-user-login
|
||||
model: authentik_stages_user_login.userloginstage
|
||||
|
|
|
@ -39,7 +39,6 @@ entries:
|
|||
model: authentik_stages_authenticator_validate.AuthenticatorValidateStage
|
||||
attrs: {}
|
||||
- identifiers:
|
||||
pk: !KeyOf default-authentication-password
|
||||
name: default-authentication-password
|
||||
id: default-authentication-password
|
||||
model: authentik_stages_password.passwordstage
|
||||
|
|
|
@ -93,7 +93,7 @@ entries:
|
|||
session_duration: seconds=0
|
||||
- identifiers:
|
||||
name: Change your password
|
||||
name: stages-prompt-password
|
||||
id: stages-prompt-password
|
||||
model: authentik_stages_prompt.promptstage
|
||||
attrs:
|
||||
fields:
|
||||
|
|
44
blueprints/system/providers-oauth2.yaml
Normal file
44
blueprints/system/providers-oauth2.yaml
Normal file
|
@ -0,0 +1,44 @@
|
|||
version: 1
|
||||
entries:
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/scope-openid
|
||||
model: authentik_providers_oauth2.ScopeMapping
|
||||
attrs:
|
||||
name: "authentik default OAuth Mapping: OpenID 'openid'"
|
||||
scope_name: openid
|
||||
expression: |
|
||||
# This scope is required by the OpenID-spec, and must as such exist in authentik.
|
||||
# The scope by itself does not grant any information
|
||||
return {}
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/scope-email
|
||||
model: authentik_providers_oauth2.ScopeMapping
|
||||
attrs:
|
||||
name: "authentik default OAuth Mapping: OpenID 'email'"
|
||||
scope_name: email
|
||||
description: "Email address"
|
||||
expression: |
|
||||
return {
|
||||
"email": request.user.email,
|
||||
"email_verified": True
|
||||
}
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/oauth2/scope-profile
|
||||
model: authentik_providers_oauth2.ScopeMapping
|
||||
attrs:
|
||||
name: "authentik default OAuth Mapping: OpenID 'profile'"
|
||||
scope_name: profile
|
||||
description: "General Profile Information"
|
||||
expression: |
|
||||
return {
|
||||
# Because authentik only saves the user's full name, and has no concept of first and last names,
|
||||
# the full name is used as given name.
|
||||
# You can override this behaviour in custom mappings, i.e. `request.user.name.split(" ")`
|
||||
"name": request.user.name,
|
||||
"given_name": request.user.name,
|
||||
"family_name": "",
|
||||
"preferred_username": request.user.username,
|
||||
"nickname": request.user.username,
|
||||
# groups is not part of the official userinfo schema, but is a quasi-standard
|
||||
"groups": [group.name for group in request.user.ak_groups.all()],
|
||||
}
|
17
blueprints/system/providers-proxy.yaml
Normal file
17
blueprints/system/providers-proxy.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
version: 1
|
||||
entries:
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/proxy/scope-proxy
|
||||
model: authentik_providers_oauth2.ScopeMapping
|
||||
attrs:
|
||||
name: "authentik default OAuth Mapping: Proxy outpost"
|
||||
scope_name: ak_proxy
|
||||
expression: |
|
||||
# This mapping is used by the authentik proxy. It passes extra user attributes,
|
||||
# which are used for example for the HTTP-Basic Authentication mapping.
|
||||
return {
|
||||
"ak_proxy": {
|
||||
"user_attributes": request.user.group_attributes(request),
|
||||
"is_superuser": request.user.is_superuser,
|
||||
}
|
||||
}
|
59
blueprints/system/providers-saml.yaml
Normal file
59
blueprints/system/providers-saml.yaml
Normal file
|
@ -0,0 +1,59 @@
|
|||
version: 1
|
||||
entries:
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/upn
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: UPN"
|
||||
saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"
|
||||
expression: |
|
||||
return request.user.attributes.get('upn', request.user.email)
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/name
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: Name"
|
||||
saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
|
||||
expression: |
|
||||
return request.user.name
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/email
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: Email"
|
||||
saml_name: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
|
||||
expression: |
|
||||
return request.user.email
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/username
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: Username"
|
||||
saml_name: "http://schemas.goauthentik.io/2021/02/saml/username"
|
||||
expression: |
|
||||
return request.user.username
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/uid
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: User ID"
|
||||
saml_name: "http://schemas.goauthentik.io/2021/02/saml/uid"
|
||||
expression: |
|
||||
return request.user.pk
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/groups
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: Groups"
|
||||
saml_name: "http://schemas.xmlsoap.org/claims/Group"
|
||||
expression: |
|
||||
for group in request.user.ak_groups.all():
|
||||
yield group.name
|
||||
- identifiers:
|
||||
managed: goauthentik.io/providers/saml/ms-windowsaccountname
|
||||
model: authentik_providers_saml.SAMLPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default SAML Mapping: WindowsAccountname (Username)"
|
||||
saml_name: "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"
|
||||
expression: |
|
||||
return request.user.username
|
68
blueprints/system/sources-ldap.yaml
Normal file
68
blueprints/system/sources-ldap.yaml
Normal file
|
@ -0,0 +1,68 @@
|
|||
version: 1
|
||||
entries:
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/default-name
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default LDAP Mapping: Name"
|
||||
object_field: "name"
|
||||
expression: |
|
||||
return ldap.get('name')
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/default-mail
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default LDAP Mapping: mail"
|
||||
object_field: "email"
|
||||
expression: |
|
||||
return ldap.get('mail')
|
||||
# ActiveDirectory-specific mappings
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/ms-samaccountname
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default Active Directory Mapping: sAMAccountName"
|
||||
object_field: "username"
|
||||
expression: |
|
||||
return ldap.get('sAMAccountName')
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/ms-userprincipalname
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default Active Directory Mapping: userPrincipalName"
|
||||
object_field: "attributes.upn"
|
||||
expression: |
|
||||
return list_flatten(ldap.get('userPrincipalName'))
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/ms-givenName
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default Active Directory Mapping: givenName"
|
||||
object_field: "attributes.givenName"
|
||||
expression: |
|
||||
return list_flatten(ldap.get('givenName'))
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/ms-sn
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default Active Directory Mapping: sn"
|
||||
object_field: "attributes.sn"
|
||||
expression: |
|
||||
return list_flatten(ldap.get('sn'))
|
||||
# OpenLDAP specific mappings
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/openldap-uid
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default OpenLDAP Mapping: uid"
|
||||
object_field: "username"
|
||||
expression: |
|
||||
return ldap.get('uid')
|
||||
- identifiers:
|
||||
managed: goauthentik.io/sources/ldap/openldap-cn
|
||||
model: authentik_sources_ldap.LDAPPropertyMapping
|
||||
attrs:
|
||||
name: "authentik default OpenLDAP Mapping: cn"
|
||||
object_field: "name"
|
||||
expression: |
|
||||
return ldap.get('cn')
|
17
schema.yml
17
schema.yml
|
@ -20866,6 +20866,11 @@ components:
|
|||
type: object
|
||||
description: Info about a single blueprint instance file
|
||||
properties:
|
||||
pk:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
title: Instance uuid
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
|
@ -20877,15 +20882,26 @@ components:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
last_applied_hash:
|
||||
type: string
|
||||
readOnly: true
|
||||
status:
|
||||
$ref: '#/components/schemas/BlueprintInstanceStatusEnum'
|
||||
enabled:
|
||||
type: boolean
|
||||
managed_models:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- context
|
||||
- last_applied
|
||||
- last_applied_hash
|
||||
- managed_models
|
||||
- name
|
||||
- path
|
||||
- pk
|
||||
- status
|
||||
BlueprintInstanceRequest:
|
||||
type: object
|
||||
|
@ -20914,6 +20930,7 @@ components:
|
|||
- successful
|
||||
- warning
|
||||
- error
|
||||
- orphaned
|
||||
- unknown
|
||||
type: string
|
||||
Cache:
|
||||
|
|
26
scripts/generate_config.py
Normal file
26
scripts/generate_config.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""Generate config for development"""
|
||||
from yaml import safe_dump
|
||||
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
with open("local.env.yml", "w") as _config:
|
||||
safe_dump(
|
||||
{
|
||||
"log_level": "debug",
|
||||
"secret_key": generate_id(),
|
||||
"postgresql": {
|
||||
"user": "postgres",
|
||||
},
|
||||
"outposts": {
|
||||
"container_image_base": "ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s",
|
||||
"blueprint_locations": ["./blueprints"],
|
||||
},
|
||||
"web": {
|
||||
"outpost_port_offset": 100,
|
||||
},
|
||||
"cert_discovery_dir": "./certs",
|
||||
"geoip": "tests/GeoLite2-City-Test.mmdb",
|
||||
},
|
||||
_config,
|
||||
default_flow_style=False,
|
||||
)
|
|
@ -13,11 +13,11 @@ from selenium.webdriver.common.keys import Keys
|
|||
from selenium.webdriver.support import expected_conditions as ec
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
from authentik.flows.models import Flow, FlowStageBinding
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
|
||||
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -25,18 +25,16 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
"""test flow with otp stages"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_totp_validate(self):
|
||||
"""test flow with otp stages"""
|
||||
sleep(1)
|
||||
# Setup TOTP Device
|
||||
device = TOTPDevice.objects.create(user=self.user, confirmed=True, digits=6)
|
||||
|
||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
FlowStageBinding.objects.create(
|
||||
target=flow, order=30, stage=AuthenticatorValidateStage.objects.create()
|
||||
)
|
||||
|
||||
self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug))
|
||||
self.login()
|
||||
|
@ -47,16 +45,17 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||
validation_stage = self.get_shadow_root("ak-stage-authenticator-validate", flow_executor)
|
||||
code_stage = self.get_shadow_root("ak-stage-authenticator-validate-code", validation_stage)
|
||||
|
||||
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(totp.token())
|
||||
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER)
|
||||
self.wait_for_url(self.if_user_url("/library"))
|
||||
self.assert_user(self.user)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_stages_authenticator_totp", "0006_default_setup_flow")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint("blueprints/default/20-flow-default-authenticator-totp-setup.yaml")
|
||||
def test_totp_setup(self):
|
||||
"""test TOTP Setup stage"""
|
||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
|
@ -98,9 +97,11 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
|||
self.assertTrue(TOTPDevice.objects.filter(user=self.user, confirmed=True).exists())
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_stages_authenticator_static", "0005_default_setup_flow")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint("blueprints/default/20-flow-default-authenticator-static-setup.yaml")
|
||||
def test_static_setup(self):
|
||||
"""test Static OTP Setup stage"""
|
||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||
|
|
|
@ -9,6 +9,7 @@ from selenium.webdriver.common.by import By
|
|||
from selenium.webdriver.support import expected_conditions as ec
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
||||
|
@ -18,7 +19,7 @@ from authentik.stages.identification.models import IdentificationStage
|
|||
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
|
||||
from authentik.stages.user_login.models import UserLoginStage
|
||||
from authentik.stages.user_write.models import UserWriteStage
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -39,8 +40,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_enroll_2_step(self):
|
||||
"""Test 2-step enroll flow"""
|
||||
# First stage fields
|
||||
|
@ -103,8 +106,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
self.assertEqual(user.email, "foo@bar.baz")
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_enroll_email(self):
|
||||
"""Test enroll with Email verification"""
|
||||
# First stage fields
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
from sys import platform
|
||||
from unittest.case import skipUnless
|
||||
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, retry
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -10,8 +11,10 @@ class TestFlowsLogin(SeleniumTestCase):
|
|||
"""test default login flow"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_login(self):
|
||||
"""test default login flow"""
|
||||
self.driver.get(
|
||||
|
|
|
@ -5,11 +5,12 @@ from unittest.case import skipUnless
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.lib.generators import generate_key
|
||||
from authentik.stages.password.models import PasswordStage
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -17,9 +18,11 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
|||
"""test stage setup flows"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_stages_password", "0002_passwordstage_change_flow")
|
||||
@apply_blueprint("blueprints/default/0-flow-password-change.yaml")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_password_change(self):
|
||||
"""test password change flow"""
|
||||
# Ensure that password stage has change_flow set
|
||||
|
|
|
@ -10,13 +10,14 @@ from guardian.shortcuts import get_anonymous_user
|
|||
from ldap3 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
|
||||
from ldap3.core.exceptions import LDAPInvalidCredentialsResult
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.models import Outpost, OutpostConfig, OutpostType
|
||||
from authentik.providers.ldap.models import APIAccessMode, LDAPProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -81,8 +82,10 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
return outpost
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_ldap_bind_success(self):
|
||||
"""Test simple bind"""
|
||||
self._prepare()
|
||||
|
@ -106,8 +109,10 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_ldap_bind_success_ssl(self):
|
||||
"""Test simple bind with ssl"""
|
||||
self._prepare()
|
||||
|
@ -131,8 +136,10 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
def test_ldap_bind_fail(self):
|
||||
"""Test simple bind (failed)"""
|
||||
self._prepare()
|
||||
|
@ -154,8 +161,11 @@ class TestProviderLDAP(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_outposts")
|
||||
def test_ldap_bind_search(self):
|
||||
"""Test simple bind + search"""
|
||||
outpost = self._prepare()
|
||||
|
|
|
@ -8,13 +8,14 @@ from docker.types import Healthcheck
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.policies.expression.models import ExpressionPolicy
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -56,10 +57,18 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_consent_implied(self):
|
||||
"""test OAuth Provider flow (default authorization flow with implied consent)"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -104,10 +113,18 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_consent_explicit(self):
|
||||
"""test OAuth Provider flow (default authorization flow with explicit consent)"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -171,10 +188,15 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_denied(self):
|
||||
"""test OAuth Provider flow (default authorization flow, denied)"""
|
||||
# Bootstrap all needed objects
|
||||
|
|
|
@ -8,6 +8,7 @@ from docker.types import Healthcheck
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert
|
||||
from authentik.flows.models import Flow
|
||||
|
@ -20,7 +21,7 @@ from authentik.providers.oauth2.constants import (
|
|||
SCOPE_OPENID_PROFILE,
|
||||
)
|
||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -65,10 +66,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_redirect_uri_error(self):
|
||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||
sleep(1)
|
||||
|
@ -106,11 +115,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_consent_implied(self):
|
||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||
sleep(1)
|
||||
|
@ -161,11 +177,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_logout(self):
|
||||
"""test OpenID Provider flow with logout"""
|
||||
sleep(1)
|
||||
|
@ -225,11 +248,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
self.driver.find_element(By.ID, "logout").click()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_consent_explicit(self):
|
||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||
sleep(1)
|
||||
|
@ -298,10 +328,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||
sleep(1)
|
||||
|
|
|
@ -10,6 +10,7 @@ from docker.types import Healthcheck
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert
|
||||
from authentik.flows.models import Flow
|
||||
|
@ -22,7 +23,7 @@ from authentik.providers.oauth2.constants import (
|
|||
SCOPE_OPENID_PROFILE,
|
||||
)
|
||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -64,10 +65,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_redirect_uri_error(self):
|
||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||
sleep(1)
|
||||
|
@ -105,11 +111,16 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def test_authorization_consent_implied(self):
|
||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||
sleep(1)
|
||||
|
@ -155,11 +166,16 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
self.assertEqual(body["UserInfo"]["email"], self.user.email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def test_authorization_consent_explicit(self):
|
||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||
sleep(1)
|
||||
|
@ -220,10 +236,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
self.assertEqual(body["UserInfo"]["email"], self.user.email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||
sleep(1)
|
||||
|
|
|
@ -10,6 +10,7 @@ from docker.types import Healthcheck
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert
|
||||
from authentik.flows.models import Flow
|
||||
|
@ -22,7 +23,7 @@ from authentik.providers.oauth2.constants import (
|
|||
SCOPE_OPENID_PROFILE,
|
||||
)
|
||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -64,10 +65,15 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_redirect_uri_error(self):
|
||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||
sleep(1)
|
||||
|
@ -105,11 +111,16 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def test_authorization_consent_implied(self):
|
||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||
sleep(1)
|
||||
|
@ -150,11 +161,16 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
self.assertEqual(body["profile"]["email"], self.user.email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||
def test_authorization_consent_explicit(self):
|
||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||
sleep(1)
|
||||
|
@ -211,10 +227,15 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
self.assertEqual(body["profile"]["email"], self.user.email)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||
sleep(1)
|
||||
|
|
|
@ -11,12 +11,13 @@ from docker.models.containers import Container
|
|||
from selenium.webdriver.common.by import By
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType
|
||||
from authentik.outposts.tasks import outpost_local_connection
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -53,11 +54,19 @@ class TestProviderProxy(SeleniumTestCase):
|
|||
return container
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-oauth2.yaml",
|
||||
"blueprints/system/providers-proxy.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_proxy_simple(self):
|
||||
"""Test simple outpost setup with single provider"""
|
||||
# set additionalHeaders to test later
|
||||
|
@ -116,11 +125,15 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
|||
"""Test Proxy connectivity over websockets"""
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_proxy_connectivity(self):
|
||||
"""Test proxy connectivity over websocket"""
|
||||
outpost_local_connection()
|
||||
|
|
|
@ -10,6 +10,7 @@ from docker.types import Healthcheck
|
|||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert
|
||||
from authentik.flows.models import Flow
|
||||
|
@ -17,7 +18,7 @@ from authentik.policies.expression.models import ExpressionPolicy
|
|||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
|
||||
from authentik.sources.saml.processors.constants import SAML_BINDING_POST
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, reconcile_app, retry
|
||||
|
||||
|
||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||
|
@ -63,11 +64,18 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
sleep(1)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_sp_initiated_implicit(self):
|
||||
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -125,11 +133,18 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_sp_initiated_explicit(self):
|
||||
"""test SAML Provider flow SP-initiated flow (explicit consent)"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -202,11 +217,18 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_sp_initiated_explicit_post(self):
|
||||
"""test SAML Provider flow SP-initiated flow (explicit consent) (POST binding)"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -279,11 +301,18 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_idp_initiated_implicit(self):
|
||||
"""test SAML Provider flow IdP-initiated flow (implicit consent)"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -347,11 +376,18 @@ class TestProviderSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/system/providers-saml.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_sp_initiated_denied(self):
|
||||
"""test SAML Provider flow SP-initiated flow (Policy denies access)"""
|
||||
# Bootstrap all needed objects
|
||||
|
|
|
@ -13,6 +13,7 @@ from selenium.webdriver.support import expected_conditions as ec
|
|||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
from yaml import safe_dump
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import User
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
|
@ -20,7 +21,7 @@ from authentik.sources.oauth.models import OAuthSource
|
|||
from authentik.sources.oauth.types.manager import MANAGER, SourceType
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
CONFIG_PATH = "/tmp/dex.yml" # nosec
|
||||
|
||||
|
@ -141,11 +142,19 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
ident_stage.save()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-source-authentication.yaml",
|
||||
"blueprints/default/20-flow-default-source-enrollment.yaml",
|
||||
"blueprints/default/20-flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_oauth_enroll(self):
|
||||
"""test OAuth Source With With OIDC"""
|
||||
self.create_objects()
|
||||
|
@ -190,11 +199,14 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"blueprints/default/20-flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
def test_oauth_enroll_auth(self):
|
||||
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
||||
self.test_oauth_enroll()
|
||||
|
@ -279,11 +291,15 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||
ident_stage.save()
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-source-authentication.yaml",
|
||||
"blueprints/default/20-flow-default-source-enrollment.yaml",
|
||||
"blueprints/default/20-flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_oauth_enroll(self):
|
||||
"""test OAuth Source With With OIDC"""
|
||||
self.create_objects()
|
||||
|
|
|
@ -11,12 +11,13 @@ from selenium.webdriver.common.keys import Keys
|
|||
from selenium.webdriver.support import expected_conditions as ec
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
from authentik.blueprints import apply_blueprint
|
||||
from authentik.core.models import User
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
|
||||
from authentik.stages.identification.models import IdentificationStage
|
||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, object_manager, retry
|
||||
from tests.e2e.utils import SeleniumTestCase, retry
|
||||
|
||||
IDP_CERT = """-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJALmVVuDWu4NYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
|
@ -94,12 +95,15 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
}
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_migration("authentik_sources_saml", "0010_samlsource_pre_authentication_flow")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-source-authentication.yaml",
|
||||
"blueprints/default/20-flow-default-source-enrollment.yaml",
|
||||
"blueprints/default/20-flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_idp_redirect(self):
|
||||
"""test SAML Source With redirect binding"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -161,12 +165,15 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_migration("authentik_sources_saml", "0010_samlsource_pre_authentication_flow")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-source-authentication.yaml",
|
||||
"blueprints/default/20-flow-default-source-enrollment.yaml",
|
||||
"blueprints/default/20-flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_idp_post(self):
|
||||
"""test SAML Source With post binding"""
|
||||
# Bootstrap all needed objects
|
||||
|
@ -241,12 +248,15 @@ class TestSourceSAML(SeleniumTestCase):
|
|||
)
|
||||
|
||||
@retry()
|
||||
@apply_migration("authentik_flows", "0008_default_flows")
|
||||
@apply_migration("authentik_flows", "0011_flow_title")
|
||||
@apply_migration("authentik_flows", "0009_source_flows")
|
||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
||||
@apply_migration("authentik_sources_saml", "0010_samlsource_pre_authentication_flow")
|
||||
@object_manager
|
||||
@apply_blueprint(
|
||||
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"blueprints/default/20-flow-default-source-authentication.yaml",
|
||||
"blueprints/default/20-flow-default-source-enrollment.yaml",
|
||||
"blueprints/default/20-flow-default-source-pre-authentication.yaml",
|
||||
)
|
||||
def test_idp_post_auto(self):
|
||||
"""test SAML Source With post binding (auto redirect)"""
|
||||
# Bootstrap all needed objects
|
||||
|
|
|
@ -10,7 +10,6 @@ from django.apps import apps
|
|||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||
from django.db import connection
|
||||
from django.db.migrations.loader import MigrationLoader
|
||||
from django.db.migrations.operations.special import RunPython
|
||||
from django.test.testcases import TransactionTestCase
|
||||
from django.urls import reverse
|
||||
from docker import DockerClient, from_env
|
||||
|
@ -25,7 +24,7 @@ from selenium.webdriver.remote.webelement import WebElement
|
|||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.manager import ObjectManager
|
||||
from authentik.blueprints.manager import ManagedAppConfig
|
||||
from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
@ -193,37 +192,22 @@ def get_loader():
|
|||
return MigrationLoader(connection)
|
||||
|
||||
|
||||
def apply_migration(app_name: str, migration_name: str):
|
||||
"""Re-apply migrations that create objects using RunPython before test cases"""
|
||||
def reconcile_app(app_name: str):
|
||||
"""Re-reconcile AppConfig methods"""
|
||||
|
||||
def wrapper_outter(func: Callable):
|
||||
"""Retry test multiple times"""
|
||||
def wrapper_outer(func: Callable):
|
||||
"""Re-reconcile AppConfig methods"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(self: TransactionTestCase, *args, **kwargs):
|
||||
migration = get_loader().get_migration(app_name, migration_name)
|
||||
with connection.schema_editor() as schema_editor:
|
||||
for operation in migration.operations:
|
||||
if not isinstance(operation, RunPython):
|
||||
continue
|
||||
operation.code(apps, schema_editor)
|
||||
config = apps.get_app_config(app_name)
|
||||
if isinstance(config, ManagedAppConfig):
|
||||
config.reconcile()
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return wrapper_outter
|
||||
|
||||
|
||||
def object_manager(func: Callable):
|
||||
"""Run objectmanager before a test function"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""Run objectmanager before a test function"""
|
||||
ObjectManager().run()
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
return wrapper_outer
|
||||
|
||||
|
||||
def retry(max_retires=RETRIES, exceptions=None):
|
||||
|
|
|
@ -13,7 +13,7 @@ title: Full development environment
|
|||
|
||||
## Services Setup
|
||||
|
||||
For PostgreSQL and Redis, you can use the docker-compose file in `scripts/`.
|
||||
For PostgreSQL and Redis, you can use the docker-compose file in `scripts/`.
|
||||
You can also use a native install, if you prefer.
|
||||
|
||||
## Backend Setup
|
||||
|
@ -23,16 +23,7 @@ poetry shell # Creates a python virtualenv, and activates it in a new shell
|
|||
poetry install # Install all required dependencies, including development dependencies
|
||||
```
|
||||
|
||||
To configure authentik to use the local databases, create a file in the authentik directory called `local.env.yml`, with the following contents
|
||||
|
||||
```yaml
|
||||
debug: true
|
||||
postgresql:
|
||||
user: postgres
|
||||
|
||||
log_level: debug
|
||||
secret_key: "A long key you can generate with `pwgen 40 1` for example"
|
||||
```
|
||||
To configure authentik to use the local databases, we need a local config file. This file can be generated by running `make gen-dev-config`.
|
||||
|
||||
To apply database migrations, run `make migrate`. This is needed after the initial setup, and whenever you fetch new source from upstream.
|
||||
|
||||
|
@ -50,7 +41,7 @@ By default, no compiled bundle of the frontend is included so this step is requi
|
|||
|
||||
To build the UI once, run `web-build`.
|
||||
|
||||
Alternatively, if you want to live-edit the UI, you can run `make web-watch` instead.
|
||||
Alternatively, if you want to live-edit the UI, you can run `make web-watch` instead.
|
||||
This will immediately update the UI with any changes you make so you can see the results in real time without needing to rebuild.
|
||||
|
||||
To format the frontend code, run `make web`.
|
||||
|
|
Reference in a new issue