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 && \
|
apt-get clean && \
|
||||||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
||||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||||
mkdir -p /certs /media && \
|
mkdir -p /certs /media /blueprints && \
|
||||||
mkdir -p /authentik/.ssh && \
|
mkdir -p /authentik/.ssh && \
|
||||||
chown authentik:authentik /certs /media /authentik/.ssh
|
chown authentik:authentik /certs /media /authentik/.ssh
|
||||||
|
|
||||||
|
@ -82,7 +82,8 @@ COPY ./pyproject.toml /
|
||||||
COPY ./xml /xml
|
COPY ./xml /xml
|
||||||
COPY ./tests /tests
|
COPY ./tests /tests
|
||||||
COPY ./manage.py /
|
COPY ./manage.py /
|
||||||
COPY ./blueprints/default /blueprints
|
COPY ./blueprints/default /blueprints/default
|
||||||
|
COPY ./blueprints/system /blueprints/system
|
||||||
COPY ./lifecycle/ /lifecycle
|
COPY ./lifecycle/ /lifecycle
|
||||||
COPY --from=builder /work/authentik /authentik-proxy
|
COPY --from=builder /work/authentik /authentik-proxy
|
||||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -33,8 +33,8 @@ test:
|
||||||
coverage report
|
coverage report
|
||||||
|
|
||||||
lint-fix:
|
lint-fix:
|
||||||
isort authentik tests lifecycle
|
isort authentik tests scripts lifecycle
|
||||||
black authentik tests lifecycle
|
black authentik tests scripts lifecycle
|
||||||
codespell -I .github/codespell-words.txt -S 'web/src/locales/**' -w \
|
codespell -I .github/codespell-words.txt -S 'web/src/locales/**' -w \
|
||||||
authentik \
|
authentik \
|
||||||
internal \
|
internal \
|
||||||
|
@ -91,6 +91,9 @@ gen-client-go:
|
||||||
go mod edit -replace goauthentik.io/api/v3=./gen-go-api
|
go mod edit -replace goauthentik.io/api/v3=./gen-go-api
|
||||||
rm -rf config.yaml ./templates/
|
rm -rf config.yaml ./templates/
|
||||||
|
|
||||||
|
gen-dev-config:
|
||||||
|
python -m scripts.generate_config
|
||||||
|
|
||||||
gen: gen-build gen-clean gen-client-web
|
gen: gen-build gen-clean gen-client-web
|
||||||
|
|
||||||
migrate:
|
migrate:
|
||||||
|
|
|
@ -16,7 +16,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
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
|
from authentik.outposts.models import Outpost
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
"""authentik admin app config"""
|
"""authentik admin app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
from prometheus_client import Gauge, Info
|
from prometheus_client import Gauge, Info
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
PROM_INFO = Info("authentik_version", "Currently running authentik version")
|
PROM_INFO = Info("authentik_version", "Currently running authentik version")
|
||||||
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
|
GAUGE_WORKERS = Gauge("authentik_admin_workers", "Currently connected workers")
|
||||||
|
|
||||||
|
|
||||||
class AuthentikAdminConfig(AppConfig):
|
class AuthentikAdminConfig(ManagedAppConfig):
|
||||||
"""authentik admin app config"""
|
"""authentik admin app config"""
|
||||||
|
|
||||||
name = "authentik.admin"
|
name = "authentik.admin"
|
||||||
label = "authentik_admin"
|
label = "authentik_admin"
|
||||||
verbose_name = "authentik Admin"
|
verbose_name = "authentik Admin"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_admin_signals(self):
|
||||||
import_module("authentik.admin.signals")
|
"""Load admin signals"""
|
||||||
|
self.import_module("authentik.admin.signals")
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
"""test admin api"""
|
"""test admin api"""
|
||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
from authentik.blueprints.tasks import managed_reconcile
|
|
||||||
from authentik.core.models import Group, User
|
from authentik.core.models import Group, User
|
||||||
from authentik.core.tasks import clean_expired_models
|
from authentik.core.tasks import clean_expired_models
|
||||||
from authentik.events.monitored_tasks import TaskResultStatus
|
from authentik.events.monitored_tasks import TaskResultStatus
|
||||||
|
@ -95,7 +95,6 @@ class TestAdminAPI(TestCase):
|
||||||
|
|
||||||
def test_system(self):
|
def test_system(self):
|
||||||
"""Test system API"""
|
"""Test system API"""
|
||||||
# pyright: reportGeneralTypeIssues=false
|
apps.get_app_config("authentik_outposts").reconcile_embedded_outpost()
|
||||||
managed_reconcile() # pylint: disable=no-value-for-parameter
|
|
||||||
response = self.client.get(reverse("authentik_api:admin_system"))
|
response = self.client.get(reverse("authentik_api:admin_system"))
|
||||||
self.assertEqual(response.status_code, 200)
|
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]:
|
def token_secret_key(value: str) -> Optional[User]:
|
||||||
"""Check if the token is the secret key
|
"""Check if the token is the secret key
|
||||||
and return the service account for the managed outpost"""
|
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:
|
if value != settings.SECRET_KEY:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Test API Authentication"""
|
"""Test API Authentication"""
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from guardian.shortcuts import get_anonymous_user
|
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.models import USER_ATTRIBUTE_SA, Token, TokenIntents
|
||||||
from authentik.core.tests.utils import create_test_flow
|
from authentik.core.tests.utils import create_test_flow
|
||||||
from authentik.lib.generators import generate_id
|
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.constants import SCOPE_AUTHENTIK_API
|
||||||
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
|
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class TestAPIAuth(TestCase):
|
||||||
with self.assertRaises(AuthenticationFailed):
|
with self.assertRaises(AuthenticationFailed):
|
||||||
user = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
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())
|
user = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
||||||
self.assertEqual(user.attributes[USER_ATTRIBUTE_SA], True)
|
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
|
model = BlueprintInstance
|
||||||
fields = [
|
fields = [
|
||||||
|
"pk",
|
||||||
"name",
|
"name",
|
||||||
"path",
|
"path",
|
||||||
"context",
|
"context",
|
||||||
"last_applied",
|
"last_applied",
|
||||||
|
"last_applied_hash",
|
||||||
"status",
|
"status",
|
||||||
"enabled",
|
"enabled",
|
||||||
|
"managed_models",
|
||||||
]
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
"last_applied": {"read_only": True},
|
||||||
|
"last_applied_hash": {"read_only": True},
|
||||||
|
"managed_models": {"read_only": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class BlueprintInstanceViewSet(ModelViewSet):
|
class BlueprintInstanceViewSet(ModelViewSet):
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
"""authentik Blueprints app"""
|
"""authentik Blueprints app"""
|
||||||
from django.apps import AppConfig
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
|
|
||||||
class AuthentikBlueprintsConfig(AppConfig):
|
class AuthentikBlueprintsConfig(ManagedAppConfig):
|
||||||
"""authentik Blueprints app"""
|
"""authentik Blueprints app"""
|
||||||
|
|
||||||
name = "authentik.blueprints"
|
name = "authentik.blueprints"
|
||||||
label = "authentik_blueprints"
|
label = "authentik_blueprints"
|
||||||
verbose_name = "authentik Blueprints"
|
verbose_name = "authentik Blueprints"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self) -> None:
|
def reconcile_load_blueprints_v1_tasks(self):
|
||||||
from authentik.blueprints.tasks import managed_reconcile
|
"""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", []):
|
for blueprint_path in options.get("blueprints", []):
|
||||||
with open(blueprint_path, "r", encoding="utf8") as blueprint_file:
|
with open(blueprint_path, "r", encoding="utf8") as blueprint_file:
|
||||||
importer = Importer(blueprint_file.read())
|
importer = Importer(blueprint_file.read())
|
||||||
valid = importer.validate()
|
valid, logs = importer.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
raise ValueError("blueprint invalid")
|
raise ValueError(f"blueprint invalid: {logs}")
|
||||||
importer.apply()
|
importer.apply()
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
|
|
|
@ -1,70 +1,37 @@
|
||||||
"""Managed objects manager"""
|
"""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 structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.blueprints.models import ManagedModel
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class EnsureOp:
|
class ManagedAppConfig(AppConfig):
|
||||||
"""Ensure operation, executed as part of an ObjectManager run"""
|
"""Basic reconciliation logic for apps"""
|
||||||
|
|
||||||
_obj: type[ManagedModel]
|
def ready(self) -> None:
|
||||||
_managed_uid: str
|
self.reconcile()
|
||||||
_kwargs: dict
|
return super().ready()
|
||||||
|
|
||||||
def __init__(self, obj: type[ManagedModel], managed_uid: str, **kwargs) -> None:
|
def import_module(self, path: str):
|
||||||
self._obj = obj
|
"""Load module"""
|
||||||
self._managed_uid = managed_uid
|
import_module(path)
|
||||||
self._kwargs = kwargs
|
|
||||||
|
|
||||||
def run(self):
|
def reconcile(self) -> None:
|
||||||
"""Do the actual ensure action"""
|
"""reconcile ourselves"""
|
||||||
raise NotImplementedError
|
prefix = "reconcile_"
|
||||||
|
for meth_name in dir(self):
|
||||||
|
meth = getattr(self, meth_name)
|
||||||
class EnsureExists(EnsureOp):
|
if not ismethod(meth):
|
||||||
"""Ensure object exists, with kwargs as given values"""
|
continue
|
||||||
|
if not meth_name.startswith(prefix):
|
||||||
created_callback: Optional[Callable]
|
continue
|
||||||
|
name = meth_name.replace(prefix, "")
|
||||||
def __init__(
|
try:
|
||||||
self,
|
meth()
|
||||||
obj: type[ManagedModel],
|
LOGGER.debug("Successfully reconciled", name=name)
|
||||||
managed_uid: str,
|
except (ProgrammingError, DatabaseError) as exc:
|
||||||
created_callback: Optional[Callable] = None,
|
LOGGER.debug("Failed to run reconcile", name=name, exc=exc)
|
||||||
**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
|
|
||||||
|
|
|
@ -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 uuid
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
|
from django.apps.registry import Apps
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def 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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = []
|
dependencies = [("authentik_flows", "0001_initial")]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
@ -38,6 +59,7 @@ class Migration(migrations.Migration):
|
||||||
("path", models.TextField()),
|
("path", models.TextField()),
|
||||||
("context", models.JSONField()),
|
("context", models.JSONField()),
|
||||||
("last_applied", models.DateTimeField(auto_now=True)),
|
("last_applied", models.DateTimeField(auto_now=True)),
|
||||||
|
("last_applied_hash", models.TextField()),
|
||||||
(
|
(
|
||||||
"status",
|
"status",
|
||||||
models.TextField(
|
models.TextField(
|
||||||
|
@ -45,6 +67,7 @@ class Migration(migrations.Migration):
|
||||||
("successful", "Successful"),
|
("successful", "Successful"),
|
||||||
("warning", "Warning"),
|
("warning", "Warning"),
|
||||||
("error", "Error"),
|
("error", "Error"),
|
||||||
|
("orphaned", "Orphaned"),
|
||||||
("unknown", "Unknown"),
|
("unknown", "Unknown"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
@ -63,4 +86,5 @@ class Migration(migrations.Migration):
|
||||||
"unique_together": {("name", "path")},
|
"unique_together": {("name", "path")},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.RunPython(migration_blueprint_import),
|
||||||
]
|
]
|
||||||
|
|
|
@ -38,6 +38,7 @@ class BlueprintInstanceStatus(models.TextChoices):
|
||||||
SUCCESSFUL = "successful"
|
SUCCESSFUL = "successful"
|
||||||
WARNING = "warning"
|
WARNING = "warning"
|
||||||
ERROR = "error"
|
ERROR = "error"
|
||||||
|
ORPHANED = "orphaned"
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
||||||
path = models.TextField()
|
path = models.TextField()
|
||||||
context = models.JSONField()
|
context = models.JSONField()
|
||||||
last_applied = models.DateTimeField(auto_now=True)
|
last_applied = models.DateTimeField(auto_now=True)
|
||||||
|
last_applied_hash = models.TextField()
|
||||||
status = models.TextField(choices=BlueprintInstanceStatus.choices)
|
status = models.TextField(choices=BlueprintInstanceStatus.choices)
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
managed_models = ArrayField(models.TextField())
|
managed_models = ArrayField(models.TextField())
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
"""managed Settings"""
|
"""blueprint Settings"""
|
||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
|
|
||||||
from authentik.lib.utils.time import fqdn_rand
|
from authentik.lib.utils.time import fqdn_rand
|
||||||
|
|
||||||
CELERY_BEAT_SCHEDULE = {
|
CELERY_BEAT_SCHEDULE = {
|
||||||
"blueprints_reconcile": {
|
"blueprints_v1_discover": {
|
||||||
"task": "authentik.blueprints.tasks.managed_reconcile",
|
"task": "authentik.blueprints.v1.tasks.blueprints_discover",
|
||||||
"schedule": crontab(minute=fqdn_rand("managed_reconcile"), hour="*/4"),
|
"schedule": crontab(minute=fqdn_rand("blueprints_v1_discover"), hour="*"),
|
||||||
"options": {"queue": "authentik_scheduled"},
|
|
||||||
},
|
|
||||||
"blueprints_config_file_discovery": {
|
|
||||||
"task": "authentik.blueprints.tasks.config_file_discovery",
|
|
||||||
"schedule": crontab(minute=fqdn_rand("config_file_discovery"), hour="*"),
|
|
||||||
"options": {"queue": "authentik_scheduled"},
|
"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)]))
|
|
|
@ -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):
|
def test_bundle_invalid_format(self):
|
||||||
"""Test bundle with invalid format"""
|
"""Test bundle with invalid format"""
|
||||||
importer = Importer('{"version": 3}')
|
importer = Importer('{"version": 3}')
|
||||||
self.assertFalse(importer.validate())
|
self.assertFalse(importer.validate()[0])
|
||||||
importer = Importer(
|
importer = Importer(
|
||||||
(
|
(
|
||||||
'{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
|
'{"version": 1,"entries":[{"identifiers":{},"attrs":{},'
|
||||||
'"model": "authentik_core.User"}]}'
|
'"model": "authentik_core.User"}]}'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertFalse(importer.validate())
|
self.assertFalse(importer.validate()[0])
|
||||||
|
|
||||||
def test_export_validate_import(self):
|
def test_export_validate_import(self):
|
||||||
"""Test export and validate it"""
|
"""Test export and validate it"""
|
||||||
|
@ -70,7 +70,7 @@ class TestFlowTransport(TransactionTestCase):
|
||||||
export_yaml = exporter.export_to_string()
|
export_yaml = exporter.export_to_string()
|
||||||
|
|
||||||
importer = Importer(export_yaml)
|
importer = Importer(export_yaml)
|
||||||
self.assertTrue(importer.validate())
|
self.assertTrue(importer.validate()[0])
|
||||||
self.assertTrue(importer.apply())
|
self.assertTrue(importer.apply())
|
||||||
|
|
||||||
self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
|
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()
|
count_initial = Prompt.objects.filter(field_key="username").count()
|
||||||
|
|
||||||
importer = Importer(STATIC_PROMPT_EXPORT)
|
importer = Importer(STATIC_PROMPT_EXPORT)
|
||||||
self.assertTrue(importer.validate())
|
self.assertTrue(importer.validate()[0])
|
||||||
self.assertTrue(importer.apply())
|
self.assertTrue(importer.apply())
|
||||||
|
|
||||||
count_before = Prompt.objects.filter(field_key="username").count()
|
count_before = Prompt.objects.filter(field_key="username").count()
|
||||||
|
@ -116,7 +116,7 @@ class TestFlowTransport(TransactionTestCase):
|
||||||
export_yaml = exporter.export_to_string()
|
export_yaml = exporter.export_to_string()
|
||||||
|
|
||||||
importer = Importer(export_yaml)
|
importer = Importer(export_yaml)
|
||||||
self.assertTrue(importer.validate())
|
self.assertTrue(importer.validate()[0])
|
||||||
self.assertTrue(importer.apply())
|
self.assertTrue(importer.apply())
|
||||||
self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
|
self.assertTrue(UserLoginStage.objects.filter(name=stage_name).exists())
|
||||||
self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
|
self.assertTrue(Flow.objects.filter(slug=flow_slug).exists())
|
||||||
|
@ -160,5 +160,5 @@ class TestFlowTransport(TransactionTestCase):
|
||||||
|
|
||||||
importer = Importer(export_yaml)
|
importer = Importer(export_yaml)
|
||||||
|
|
||||||
self.assertTrue(importer.validate())
|
self.assertTrue(importer.validate()[0])
|
||||||
self.assertTrue(importer.apply())
|
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.exceptions import ValidationError
|
||||||
from rest_framework.serializers import BaseSerializer, Serializer
|
from rest_framework.serializers import BaseSerializer, Serializer
|
||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
from structlog.testing import capture_logs
|
||||||
|
from structlog.types import EventDict
|
||||||
from yaml import load
|
from yaml import load
|
||||||
|
|
||||||
from authentik.blueprints.v1.common import (
|
from authentik.blueprints.v1.common import (
|
||||||
|
@ -198,17 +200,20 @@ class Importer:
|
||||||
self.logger.debug("updated model", model=model, pk=model.pk)
|
self.logger.debug("updated model", model=model, pk=model.pk)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def validate(self) -> bool:
|
def validate(self) -> tuple[bool, list[EventDict]]:
|
||||||
"""Validate loaded flow export, ensure all models are allowed
|
"""Validate loaded blueprint export, ensure all models are allowed
|
||||||
and serializers have no errors"""
|
and serializers have no errors"""
|
||||||
self.logger.debug("Starting flow import validation")
|
self.logger.debug("Starting blueprint import validation")
|
||||||
orig_import = deepcopy(self.__import)
|
orig_import = deepcopy(self.__import)
|
||||||
if self.__import.version != 1:
|
if self.__import.version != 1:
|
||||||
self.logger.warning("Invalid bundle version")
|
self.logger.warning("Invalid bundle version")
|
||||||
return False
|
return False, []
|
||||||
with transaction_rollback():
|
with (
|
||||||
|
transaction_rollback(),
|
||||||
|
capture_logs() as logs,
|
||||||
|
):
|
||||||
successful = self._apply_models()
|
successful = self._apply_models()
|
||||||
if not successful:
|
if not successful:
|
||||||
self.logger.debug("Flow validation failed")
|
self.logger.debug("blueprint validation failed")
|
||||||
self.__import = orig_import
|
self.__import = orig_import
|
||||||
return successful
|
return successful, logs
|
||||||
|
|
|
@ -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"""
|
"""authentik core app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
class AuthentikCoreConfig(AppConfig):
|
|
||||||
|
class AuthentikCoreConfig(ManagedAppConfig):
|
||||||
"""authentik core app config"""
|
"""authentik core app config"""
|
||||||
|
|
||||||
name = "authentik.core"
|
name = "authentik.core"
|
||||||
label = "authentik_core"
|
label = "authentik_core"
|
||||||
verbose_name = "authentik Core"
|
verbose_name = "authentik Core"
|
||||||
mountpoint = ""
|
mountpoint = ""
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_core_signals(self):
|
||||||
import_module("authentik.core.signals")
|
"""Load core signals"""
|
||||||
import_module("authentik.core.managed")
|
self.import_module("authentik.core.signals")
|
||||||
|
|
||||||
|
def reconcile_debug_worker_hook(self):
|
||||||
|
"""Dispatch startup tasks inline when debugging"""
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
from authentik.root.celery import worker_ready_hook
|
from authentik.root.celery import worker_ready_hook
|
||||||
|
|
||||||
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.api.decorators import permission_required
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
from authentik.crypto.apps import MANAGED_KEY
|
||||||
from authentik.crypto.builder import CertificateBuilder
|
from authentik.crypto.builder import CertificateBuilder
|
||||||
from authentik.crypto.managed import MANAGED_KEY
|
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,55 @@
|
||||||
"""authentik crypto app config"""
|
"""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"""
|
"""authentik crypto app config"""
|
||||||
|
|
||||||
name = "authentik.crypto"
|
name = "authentik.crypto"
|
||||||
label = "authentik_crypto"
|
label = "authentik_crypto"
|
||||||
verbose_name = "authentik Crypto"
|
verbose_name = "authentik Crypto"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_crypto_tasks(self):
|
||||||
import_module("authentik.crypto.managed")
|
"""Load crypto tasks"""
|
||||||
import_module("authentik.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"""
|
"""authentik events app"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
from prometheus_client import Gauge
|
from prometheus_client import Gauge
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
GAUGE_TASKS = Gauge(
|
GAUGE_TASKS = Gauge(
|
||||||
"authentik_system_tasks",
|
"authentik_system_tasks",
|
||||||
"System tasks and their status",
|
"System tasks and their status",
|
||||||
|
@ -11,12 +10,14 @@ GAUGE_TASKS = Gauge(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthentikEventsConfig(AppConfig):
|
class AuthentikEventsConfig(ManagedAppConfig):
|
||||||
"""authentik events app"""
|
"""authentik events app"""
|
||||||
|
|
||||||
name = "authentik.events"
|
name = "authentik.events"
|
||||||
label = "authentik_events"
|
label = "authentik_events"
|
||||||
verbose_name = "authentik Events"
|
verbose_name = "authentik Events"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_events_signals(self):
|
||||||
import_module("authentik.events.signals")
|
"""Load events signals"""
|
||||||
|
self.import_module("authentik.events.signals")
|
||||||
|
|
|
@ -168,7 +168,8 @@ class FlowViewSet(UsedByMixin, ModelViewSet):
|
||||||
if not file:
|
if not file:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
importer = Importer(file.read().decode())
|
importer = Importer(file.read().decode())
|
||||||
valid = importer.validate()
|
valid, _logs = importer.validate()
|
||||||
|
# TODO: return logs
|
||||||
if not valid:
|
if not valid:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
successful = importer.apply()
|
successful = importer.apply()
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
"""authentik flows app config"""
|
"""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 prometheus_client import Gauge, Histogram
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
from authentik.lib.utils.reflection import all_subclasses
|
from authentik.lib.utils.reflection import all_subclasses
|
||||||
|
|
||||||
GAUGE_FLOWS_CACHED = Gauge(
|
GAUGE_FLOWS_CACHED = Gauge(
|
||||||
|
@ -18,20 +15,22 @@ HIST_FLOWS_PLAN_TIME = Histogram(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthentikFlowsConfig(AppConfig):
|
class AuthentikFlowsConfig(ManagedAppConfig):
|
||||||
"""authentik flows app config"""
|
"""authentik flows app config"""
|
||||||
|
|
||||||
name = "authentik.flows"
|
name = "authentik.flows"
|
||||||
label = "authentik_flows"
|
label = "authentik_flows"
|
||||||
mountpoint = "flows/"
|
mountpoint = "flows/"
|
||||||
verbose_name = "authentik Flows"
|
verbose_name = "authentik Flows"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_flows_signals(self):
|
||||||
import_module("authentik.flows.signals")
|
"""Load flows signals"""
|
||||||
try:
|
self.import_module("authentik.flows.signals")
|
||||||
from authentik.flows.models import Stage
|
|
||||||
|
|
||||||
for stage in all_subclasses(Stage):
|
def reconcile_stages_loaded(self):
|
||||||
_ = stage().type
|
"""Ensure all stages are loaded"""
|
||||||
except ProgrammingError:
|
from authentik.flows.models import Stage
|
||||||
pass
|
|
||||||
|
for stage in all_subclasses(Stage):
|
||||||
|
_ = stage().type
|
||||||
|
|
|
@ -62,7 +62,6 @@ ldap:
|
||||||
tls:
|
tls:
|
||||||
ciphers: null
|
ciphers: null
|
||||||
|
|
||||||
config_file_dir: "/config"
|
|
||||||
cookie_domain: null
|
cookie_domain: null
|
||||||
disable_update_check: false
|
disable_update_check: false
|
||||||
disable_startup_analytics: false
|
disable_startup_analytics: false
|
||||||
|
@ -79,3 +78,6 @@ gdpr_compliance: true
|
||||||
cert_discovery_dir: /certs
|
cert_discovery_dir: /certs
|
||||||
default_token_length: 128
|
default_token_length: 128
|
||||||
impersonation: true
|
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.api.utils import PassiveSerializer, is_dict
|
||||||
from authentik.core.models import Provider
|
from authentik.core.models import Provider
|
||||||
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
|
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.outposts.models import Outpost, OutpostConfig, OutpostType, default_outpost_config
|
||||||
from authentik.providers.ldap.models import LDAPProvider
|
from authentik.providers.ldap.models import LDAPProvider
|
||||||
from authentik.providers.proxy.models import ProxyProvider
|
from authentik.providers.proxy.models import ProxyProvider
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
"""authentik outposts app config"""
|
"""authentik outposts app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
from prometheus_client import Gauge
|
from prometheus_client import Gauge
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
GAUGE_OUTPOSTS_CONNECTED = Gauge(
|
GAUGE_OUTPOSTS_CONNECTED = Gauge(
|
||||||
|
@ -15,15 +14,47 @@ GAUGE_OUTPOSTS_LAST_UPDATE = Gauge(
|
||||||
"Last update from any outpost",
|
"Last update from any outpost",
|
||||||
["outpost", "uid", "version"],
|
["outpost", "uid", "version"],
|
||||||
)
|
)
|
||||||
|
MANAGED_OUTPOST = "goauthentik.io/outposts/embedded"
|
||||||
|
|
||||||
|
|
||||||
class AuthentikOutpostConfig(AppConfig):
|
class AuthentikOutpostConfig(ManagedAppConfig):
|
||||||
"""authentik outposts app config"""
|
"""authentik outposts app config"""
|
||||||
|
|
||||||
name = "authentik.outposts"
|
name = "authentik.outposts"
|
||||||
label = "authentik_outposts"
|
label = "authentik_outposts"
|
||||||
verbose_name = "authentik Outpost"
|
verbose_name = "authentik Outpost"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_outposts_signals(self):
|
||||||
import_module("authentik.outposts.signals")
|
"""Load outposts signals"""
|
||||||
import_module("authentik.outposts.managed")
|
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 yaml import safe_dump
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
|
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||||
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
|
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
|
||||||
from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException
|
from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException
|
||||||
from authentik.outposts.docker_tls import DockerInlineTLS
|
from authentik.outposts.docker_tls import DockerInlineTLS
|
||||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
|
||||||
from authentik.outposts.models import (
|
from authentik.outposts.models import (
|
||||||
DockerServiceConnection,
|
DockerServiceConnection,
|
||||||
Outpost,
|
Outpost,
|
||||||
|
|
|
@ -10,8 +10,8 @@ from structlog.stdlib import get_logger
|
||||||
from urllib3.exceptions import HTTPError
|
from urllib3.exceptions import HTTPError
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
|
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||||
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate
|
from authentik.outposts.controllers.k8s.triggers import NeedsRecreate, NeedsUpdate
|
||||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||||
|
|
|
@ -78,7 +78,7 @@ class DockerInlineSSH:
|
||||||
"""Cleanup when we're done"""
|
"""Cleanup when we're done"""
|
||||||
try:
|
try:
|
||||||
os.unlink(self.key_path)
|
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
|
start = 0
|
||||||
end = 0
|
end = 0
|
||||||
lines = ssh_config.readlines()
|
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():
|
def outpost_local_connection():
|
||||||
"""Checks the local environment and create Service connections."""
|
"""Checks the local environment and create Service connections."""
|
||||||
if not CONFIG.y_bool("outposts.discover"):
|
if not CONFIG.y_bool("outposts.discover"):
|
||||||
LOGGER.debug("outpost integration discovery is disabled")
|
LOGGER.debug("Outpost integration discovery is disabled")
|
||||||
return
|
return
|
||||||
# Explicitly check against token filename, as that's
|
# Explicitly check against token filename, as that's
|
||||||
# only present when the integration is enabled
|
# only present when the integration is enabled
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
"""Docker controller tests"""
|
"""Docker controller tests"""
|
||||||
|
from django.apps import apps
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from docker.models.containers import Container
|
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.base import ControllerException
|
||||||
from authentik.outposts.controllers.docker import DockerController
|
from authentik.outposts.controllers.docker import DockerController
|
||||||
from authentik.outposts.managed import MANAGED_OUTPOST
|
|
||||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
|
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostType
|
||||||
from authentik.providers.proxy.controllers.docker import ProxyDockerController
|
from authentik.providers.proxy.controllers.docker import ProxyDockerController
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class DockerControllerTests(TestCase):
|
||||||
type=OutpostType.PROXY,
|
type=OutpostType.PROXY,
|
||||||
)
|
)
|
||||||
self.integration = DockerServiceConnection(name="test")
|
self.integration = DockerServiceConnection(name="test")
|
||||||
ObjectManager().run()
|
apps.get_app_config("authentik_outposts").reconcile()
|
||||||
|
|
||||||
def test_init_managed(self):
|
def test_init_managed(self):
|
||||||
"""Docker controller shouldn't do anything for managed outpost"""
|
"""Docker controller shouldn't do anything for managed outpost"""
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
"""authentik policies app config"""
|
"""authentik policies app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
from prometheus_client import Gauge, Histogram
|
from prometheus_client import Gauge, Histogram
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
GAUGE_POLICIES_CACHED = Gauge(
|
GAUGE_POLICIES_CACHED = Gauge(
|
||||||
"authentik_policies_cached",
|
"authentik_policies_cached",
|
||||||
"Cached Policies",
|
"Cached Policies",
|
||||||
|
@ -27,12 +26,14 @@ HIST_POLICIES_EXECUTION_TIME = Histogram(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthentikPoliciesConfig(AppConfig):
|
class AuthentikPoliciesConfig(ManagedAppConfig):
|
||||||
"""authentik policies app config"""
|
"""authentik policies app config"""
|
||||||
|
|
||||||
name = "authentik.policies"
|
name = "authentik.policies"
|
||||||
label = "authentik_policies"
|
label = "authentik_policies"
|
||||||
verbose_name = "authentik Policies"
|
verbose_name = "authentik Policies"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_policies_signals(self):
|
||||||
import_module("authentik.policies.signals")
|
"""Load policies signals"""
|
||||||
|
self.import_module("authentik.policies.signals")
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
"""Authentik reputation_policy app config"""
|
"""Authentik reputation_policy app config"""
|
||||||
from importlib import import_module
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AuthentikPolicyReputationConfig(AppConfig):
|
class AuthentikPolicyReputationConfig(ManagedAppConfig):
|
||||||
"""Authentik reputation app config"""
|
"""Authentik reputation app config"""
|
||||||
|
|
||||||
name = "authentik.policies.reputation"
|
name = "authentik.policies.reputation"
|
||||||
label = "authentik_policies_reputation"
|
label = "authentik_policies_reputation"
|
||||||
verbose_name = "authentik Policies.Reputation"
|
verbose_name = "authentik Policies.Reputation"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_policies_reputation_signals(self):
|
||||||
import_module("authentik.policies.reputation.signals")
|
"""Load policies.reputation signals"""
|
||||||
import_module("authentik.policies.reputation.tasks")
|
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"""
|
"""authentik oauth provider app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +12,3 @@ class AuthentikProviderOAuth2Config(AppConfig):
|
||||||
"authentik.providers.oauth2.urls_github": "",
|
"authentik.providers.oauth2.urls_github": "",
|
||||||
"authentik.providers.oauth2.urls": "application/o/",
|
"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 django.urls import reverse
|
||||||
from jwt import decode
|
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.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.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
|
@ -24,9 +24,9 @@ from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||||
class TestTokenClientCredentials(OAuthTestCase):
|
class TestTokenClientCredentials(OAuthTestCase):
|
||||||
"""Test token (client_credentials) view"""
|
"""Test token (client_credentials) view"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
ObjectManager().run()
|
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.provider = OAuth2Provider.objects.create(
|
self.provider = OAuth2Provider.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.test import RequestFactory
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from jwt import decode
|
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.models import Application, Group
|
||||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
|
@ -26,9 +26,9 @@ from authentik.sources.oauth.models import OAuthSource
|
||||||
class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
class TestTokenClientCredentialsJWTSource(OAuthTestCase):
|
||||||
"""Test token (client_credentials, with JWT) view"""
|
"""Test token (client_credentials, with JWT) view"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
ObjectManager().run()
|
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.cert = create_test_cert()
|
self.cert = create_test_cert()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from dataclasses import asdict
|
||||||
|
|
||||||
from django.urls import reverse
|
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.models import Application
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
@ -16,9 +16,9 @@ from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||||
class TestUserinfo(OAuthTestCase):
|
class TestUserinfo(OAuthTestCase):
|
||||||
"""Test token view"""
|
"""Test token view"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/providers-oauth2.yaml")
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
ObjectManager().run()
|
|
||||||
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
|
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
|
||||||
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
|
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
"""authentik Proxy app"""
|
"""authentik Proxy app"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +8,3 @@ class AuthentikProviderProxyConfig(AppConfig):
|
||||||
name = "authentik.providers.proxy"
|
name = "authentik.providers.proxy"
|
||||||
label = "authentik_providers_proxy"
|
label = "authentik_providers_proxy"
|
||||||
verbose_name = "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"""
|
"""authentik SAML IdP app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
@ -11,6 +10,3 @@ class AuthentikProviderSAMLConfig(AppConfig):
|
||||||
label = "authentik_providers_saml"
|
label = "authentik_providers_saml"
|
||||||
verbose_name = "authentik Providers.SAML"
|
verbose_name = "authentik Providers.SAML"
|
||||||
mountpoint = "application/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.http.request import QueryDict
|
||||||
from django.test import RequestFactory, TestCase
|
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.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
@ -74,8 +74,8 @@ qNAZMq1DqpibfCBg
|
||||||
class TestAuthNRequest(TestCase):
|
class TestAuthNRequest(TestCase):
|
||||||
"""Test AuthN Request generator and parser"""
|
"""Test AuthN Request generator and parser"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/providers-saml.yaml")
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ObjectManager().run()
|
|
||||||
cert = create_test_cert()
|
cert = create_test_cert()
|
||||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||||
authorization_flow=create_test_flow(),
|
authorization_flow=create_test_flow(),
|
||||||
|
|
|
@ -4,7 +4,7 @@ from base64 import b64encode
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
from lxml import etree # nosec
|
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.core.tests.utils import create_test_cert, create_test_flow
|
||||||
from authentik.lib.tests.utils import get_request
|
from authentik.lib.tests.utils import get_request
|
||||||
from authentik.lib.xml import lxml_from_string
|
from authentik.lib.xml import lxml_from_string
|
||||||
|
@ -18,8 +18,8 @@ from authentik.sources.saml.processors.request import RequestProcessor
|
||||||
class TestSchema(TestCase):
|
class TestSchema(TestCase):
|
||||||
"""Test Requests and Responses against schema"""
|
"""Test Requests and Responses against schema"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/providers-saml.yaml")
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ObjectManager().run()
|
|
||||||
cert = create_test_cert()
|
cert = create_test_cert()
|
||||||
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
self.provider: SAMLProvider = SAMLProvider.objects.create(
|
||||||
authorization_flow=create_test_flow(),
|
authorization_flow=create_test_flow(),
|
||||||
|
|
|
@ -58,6 +58,7 @@ def task_prerun_hook(task_id: str, task, *args, **kwargs):
|
||||||
@task_postrun.connect
|
@task_postrun.connect
|
||||||
def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
|
def task_postrun_hook(task_id, task, *args, retval=None, state=None, **kwargs):
|
||||||
"""Log task_id on worker"""
|
"""Log task_id on worker"""
|
||||||
|
CTX_TASK_ID.set(...)
|
||||||
LOGGER.info("Task finished", task_id=task_id, task_name=task.__name__, state=state)
|
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
|
from authentik.events.models import Event, EventAction
|
||||||
|
|
||||||
LOGGER.warning("Task failure", exc=exception)
|
LOGGER.warning("Task failure", exc=exception)
|
||||||
|
CTX_TASK_ID.set(...)
|
||||||
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
|
||||||
Event.new(EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception)).save()
|
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]:
|
def _get_startup_tasks() -> list[Callable]:
|
||||||
"""Get all tasks to be run on startup"""
|
"""Get all tasks to be run on startup"""
|
||||||
from authentik.admin.tasks import clear_update_notifications
|
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.outposts.tasks import outpost_controller_all, outpost_local_connection
|
||||||
from authentik.providers.proxy.tasks import proxy_set_defaults
|
from authentik.providers.proxy.tasks import proxy_set_defaults
|
||||||
|
|
||||||
|
@ -85,7 +86,6 @@ def _get_startup_tasks() -> list[Callable]:
|
||||||
outpost_local_connection,
|
outpost_local_connection,
|
||||||
outpost_controller_all,
|
outpost_controller_all,
|
||||||
proxy_set_defaults,
|
proxy_set_defaults,
|
||||||
managed_reconcile,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
"""authentik ldap source config"""
|
"""authentik ldap source config"""
|
||||||
from importlib import import_module
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AuthentikSourceLDAPConfig(AppConfig):
|
class AuthentikSourceLDAPConfig(ManagedAppConfig):
|
||||||
"""Authentik ldap app config"""
|
"""Authentik ldap app config"""
|
||||||
|
|
||||||
name = "authentik.sources.ldap"
|
name = "authentik.sources.ldap"
|
||||||
label = "authentik_sources_ldap"
|
label = "authentik_sources_ldap"
|
||||||
verbose_name = "authentik Sources.LDAP"
|
verbose_name = "authentik Sources.LDAP"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_sources_ldap_signals(self):
|
||||||
import_module("authentik.sources.ldap.signals")
|
"""Load sources.ldap signals"""
|
||||||
import_module("authentik.sources.ldap.managed")
|
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.db.models import Q
|
||||||
from django.test import TestCase
|
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.core.models import User
|
||||||
from authentik.lib.generators import generate_key
|
from authentik.lib.generators import generate_key
|
||||||
from authentik.sources.ldap.auth import LDAPBackend
|
from authentik.sources.ldap.auth import LDAPBackend
|
||||||
|
@ -19,8 +19,8 @@ LDAP_PASSWORD = generate_key()
|
||||||
class LDAPSyncTests(TestCase):
|
class LDAPSyncTests(TestCase):
|
||||||
"""LDAP Sync tests"""
|
"""LDAP Sync tests"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/sources-ldap.yaml")
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ObjectManager().run()
|
|
||||||
self.source = LDAPSource.objects.create(
|
self.source = LDAPSource.objects.create(
|
||||||
name="ldap",
|
name="ldap",
|
||||||
slug="ldap",
|
slug="ldap",
|
||||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import PropertyMock, patch
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.test import TestCase
|
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.models import Group, User
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
@ -23,8 +23,8 @@ LDAP_PASSWORD = generate_key()
|
||||||
class LDAPSyncTests(TestCase):
|
class LDAPSyncTests(TestCase):
|
||||||
"""LDAP Sync tests"""
|
"""LDAP Sync tests"""
|
||||||
|
|
||||||
|
@apply_blueprint("blueprints/system/sources-ldap.yaml")
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ObjectManager().run()
|
|
||||||
self.source: LDAPSource = LDAPSource.objects.create(
|
self.source: LDAPSource = LDAPSource.objects.create(
|
||||||
name="ldap",
|
name="ldap",
|
||||||
slug="ldap",
|
slug="ldap",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
"""authentik oauth_client config"""
|
"""authentik oauth_client config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
||||||
|
@ -21,18 +20,19 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AuthentikSourceOAuthConfig(AppConfig):
|
class AuthentikSourceOAuthConfig(ManagedAppConfig):
|
||||||
"""authentik source.oauth config"""
|
"""authentik source.oauth config"""
|
||||||
|
|
||||||
name = "authentik.sources.oauth"
|
name = "authentik.sources.oauth"
|
||||||
label = "authentik_sources_oauth"
|
label = "authentik_sources_oauth"
|
||||||
verbose_name = "authentik Sources.OAuth"
|
verbose_name = "authentik Sources.OAuth"
|
||||||
mountpoint = "source/oauth/"
|
mountpoint = "source/oauth/"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_sources_loaded(self):
|
||||||
"""Load source_types from config file"""
|
"""Load source_types from config file"""
|
||||||
for source_type in AUTHENTIK_SOURCES_OAUTH_TYPES:
|
for source_type in AUTHENTIK_SOURCES_OAUTH_TYPES:
|
||||||
try:
|
try:
|
||||||
import_module(source_type)
|
self.import_module(source_type)
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
LOGGER.warning("Failed to load OAuth Source", exc=exc)
|
LOGGER.warning("Failed to load OAuth Source", exc=exc)
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
"""Authentik SAML app config"""
|
"""Authentik SAML app config"""
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AuthentikSourceSAMLConfig(AppConfig):
|
class AuthentikSourceSAMLConfig(ManagedAppConfig):
|
||||||
"""authentik saml source app config"""
|
"""authentik saml source app config"""
|
||||||
|
|
||||||
name = "authentik.sources.saml"
|
name = "authentik.sources.saml"
|
||||||
label = "authentik_sources_saml"
|
label = "authentik_sources_saml"
|
||||||
verbose_name = "authentik Sources.SAML"
|
verbose_name = "authentik Sources.SAML"
|
||||||
mountpoint = "source/saml/"
|
mountpoint = "source/saml/"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_sources_saml_signals(self):
|
||||||
import_module("authentik.sources.saml.signals")
|
"""Load sources.saml signals"""
|
||||||
|
self.import_module("authentik.sources.saml.signals")
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
"""Authenticator Static stage"""
|
"""Authenticator Static stage"""
|
||||||
from importlib import import_module
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class AuthentikStageAuthenticatorStaticConfig(AppConfig):
|
class AuthentikStageAuthenticatorStaticConfig(ManagedAppConfig):
|
||||||
"""Authenticator Static stage"""
|
"""Authenticator Static stage"""
|
||||||
|
|
||||||
name = "authentik.stages.authenticator_static"
|
name = "authentik.stages.authenticator_static"
|
||||||
label = "authentik_stages_authenticator_static"
|
label = "authentik_stages_authenticator_static"
|
||||||
verbose_name = "authentik Stages.Authenticator.Static"
|
verbose_name = "authentik Stages.Authenticator.Static"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_stages_authenticator_static_signals(self):
|
||||||
import_module("authentik.stages.authenticator_static.signals")
|
"""Load stages.authenticator_static signals"""
|
||||||
|
self.import_module("authentik.stages.authenticator_static.signals")
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
"""authentik email stage config"""
|
"""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.exceptions import TemplateDoesNotExist
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.blueprints.manager import ManagedAppConfig
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class AuthentikStageEmailConfig(AppConfig):
|
class AuthentikStageEmailConfig(ManagedAppConfig):
|
||||||
"""authentik email stage config"""
|
"""authentik email stage config"""
|
||||||
|
|
||||||
name = "authentik.stages.email"
|
name = "authentik.stages.email"
|
||||||
label = "authentik_stages_email"
|
label = "authentik_stages_email"
|
||||||
verbose_name = "authentik Stages.Email"
|
verbose_name = "authentik Stages.Email"
|
||||||
|
default = True
|
||||||
|
|
||||||
def ready(self):
|
def reconcile_load_stages_emails_tasks(self):
|
||||||
import_module("authentik.stages.email.tasks")
|
"""Load stages.emails tasks"""
|
||||||
try:
|
self.import_module("authentik.stages.email.tasks")
|
||||||
self.validate_stage_templates()
|
|
||||||
except ProgrammingError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def validate_stage_templates(self):
|
def reconcile_stage_templates_valid(self):
|
||||||
"""Ensure all stage's templates actually exist"""
|
"""Ensure all stage's templates actually exist"""
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.stages.email.models import EmailStage, EmailTemplates
|
from authentik.stages.email.models import EmailStage, EmailTemplates
|
||||||
|
|
|
@ -27,7 +27,7 @@ entries:
|
||||||
expression: |
|
expression: |
|
||||||
# Check if we''ve not been given a username by the external IdP
|
# Check if we''ve not been given a username by the external IdP
|
||||||
# and trigger the enrollment flow
|
# 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
|
meta_model_name: authentik_policies_expression.expressionpolicy
|
||||||
identifiers:
|
identifiers:
|
||||||
name: default-source-enrollment-if-username
|
name: default-source-enrollment-if-username
|
||||||
|
@ -78,7 +78,7 @@ entries:
|
||||||
order: 0
|
order: 0
|
||||||
stage: !KeyOf default-source-enrollment-prompt
|
stage: !KeyOf default-source-enrollment-prompt
|
||||||
target: !KeyOf flow
|
target: !KeyOf flow
|
||||||
id: prompt-binding
|
id: prompt-binding
|
||||||
model: authentik_flows.flowstagebinding
|
model: authentik_flows.flowstagebinding
|
||||||
- attrs:
|
- attrs:
|
||||||
evaluate_on_plan: true
|
evaluate_on_plan: true
|
||||||
|
|
|
@ -5,7 +5,7 @@ entries:
|
||||||
layout: stacked
|
layout: stacked
|
||||||
name: Pre-Authentication
|
name: Pre-Authentication
|
||||||
policy_engine_mode: any
|
policy_engine_mode: any
|
||||||
title: ''
|
title: Pre-authentication
|
||||||
identifiers:
|
identifiers:
|
||||||
slug: default-source-pre-authentication
|
slug: default-source-pre-authentication
|
||||||
model: authentik_flows.flow
|
model: authentik_flows.flow
|
||||||
|
|
|
@ -3,9 +3,9 @@ entries:
|
||||||
compatibility_mode: false
|
compatibility_mode: false
|
||||||
designation: stage_configuration
|
designation: stage_configuration
|
||||||
layout: stacked
|
layout: stacked
|
||||||
name: Update your info
|
name: User settings
|
||||||
policy_engine_mode: any
|
policy_engine_mode: any
|
||||||
title: ''
|
title: Update your info
|
||||||
identifiers:
|
identifiers:
|
||||||
slug: default-user-settings-flow
|
slug: default-user-settings-flow
|
||||||
model: authentik_flows.flow
|
model: authentik_flows.flow
|
||||||
|
@ -108,9 +108,9 @@ entries:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
meta_model_name: authentik_policies_expression.expressionpolicy
|
meta_model_name: authentik_policies_expression.expressionpolicy
|
||||||
name: default-user-settings-authorization
|
|
||||||
identifiers:
|
identifiers:
|
||||||
name: default-user-settings-authorization
|
name: default-user-settings-authorization
|
||||||
|
id: default-user-settings-authorization
|
||||||
model: authentik_policies_expression.expressionpolicy
|
model: authentik_policies_expression.expressionpolicy
|
||||||
- attrs:
|
- attrs:
|
||||||
create_users_as_inactive: false
|
create_users_as_inactive: false
|
||||||
|
|
|
@ -76,7 +76,6 @@ entries:
|
||||||
- !KeyOf prompt-field-password
|
- !KeyOf prompt-field-password
|
||||||
- !KeyOf prompt-field-password-repeat
|
- !KeyOf prompt-field-password-repeat
|
||||||
- identifiers:
|
- identifiers:
|
||||||
pk: !KeyOf default-enrollment-user-login
|
|
||||||
name: default-enrollment-user-login
|
name: default-enrollment-user-login
|
||||||
id: default-enrollment-user-login
|
id: default-enrollment-user-login
|
||||||
model: authentik_stages_user_login.userloginstage
|
model: authentik_stages_user_login.userloginstage
|
||||||
|
|
|
@ -39,7 +39,6 @@ entries:
|
||||||
model: authentik_stages_authenticator_validate.AuthenticatorValidateStage
|
model: authentik_stages_authenticator_validate.AuthenticatorValidateStage
|
||||||
attrs: {}
|
attrs: {}
|
||||||
- identifiers:
|
- identifiers:
|
||||||
pk: !KeyOf default-authentication-password
|
|
||||||
name: default-authentication-password
|
name: default-authentication-password
|
||||||
id: default-authentication-password
|
id: default-authentication-password
|
||||||
model: authentik_stages_password.passwordstage
|
model: authentik_stages_password.passwordstage
|
||||||
|
|
|
@ -93,7 +93,7 @@ entries:
|
||||||
session_duration: seconds=0
|
session_duration: seconds=0
|
||||||
- identifiers:
|
- identifiers:
|
||||||
name: Change your password
|
name: Change your password
|
||||||
name: stages-prompt-password
|
id: stages-prompt-password
|
||||||
model: authentik_stages_prompt.promptstage
|
model: authentik_stages_prompt.promptstage
|
||||||
attrs:
|
attrs:
|
||||||
fields:
|
fields:
|
||||||
|
|
|
@ -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()],
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
type: object
|
||||||
description: Info about a single blueprint instance file
|
description: Info about a single blueprint instance file
|
||||||
properties:
|
properties:
|
||||||
|
pk:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
title: Instance uuid
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
path:
|
path:
|
||||||
|
@ -20877,15 +20882,26 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
last_applied_hash:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
status:
|
status:
|
||||||
$ref: '#/components/schemas/BlueprintInstanceStatusEnum'
|
$ref: '#/components/schemas/BlueprintInstanceStatusEnum'
|
||||||
enabled:
|
enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
managed_models:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- context
|
- context
|
||||||
- last_applied
|
- last_applied
|
||||||
|
- last_applied_hash
|
||||||
|
- managed_models
|
||||||
- name
|
- name
|
||||||
- path
|
- path
|
||||||
|
- pk
|
||||||
- status
|
- status
|
||||||
BlueprintInstanceRequest:
|
BlueprintInstanceRequest:
|
||||||
type: object
|
type: object
|
||||||
|
@ -20914,6 +20930,7 @@ components:
|
||||||
- successful
|
- successful
|
||||||
- warning
|
- warning
|
||||||
- error
|
- error
|
||||||
|
- orphaned
|
||||||
- unknown
|
- unknown
|
||||||
type: string
|
type: string
|
||||||
Cache:
|
Cache:
|
||||||
|
|
|
@ -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 import expected_conditions as ec
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
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_static.models import AuthenticatorStaticStage
|
||||||
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
|
||||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
|
from tests.e2e.utils import SeleniumTestCase, retry
|
||||||
from tests.e2e.utils import SeleniumTestCase, apply_migration, retry
|
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -25,18 +25,16 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||||
"""test flow with otp stages"""
|
"""test flow with otp stages"""
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_totp_validate(self):
|
def test_totp_validate(self):
|
||||||
"""test flow with otp stages"""
|
"""test flow with otp stages"""
|
||||||
sleep(1)
|
|
||||||
# Setup TOTP Device
|
# Setup TOTP Device
|
||||||
device = TOTPDevice.objects.create(user=self.user, confirmed=True, digits=6)
|
device = TOTPDevice.objects.create(user=self.user, confirmed=True, digits=6)
|
||||||
|
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
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.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug))
|
||||||
self.login()
|
self.login()
|
||||||
|
@ -47,16 +45,17 @@ class TestFlowsAuthenticator(SeleniumTestCase):
|
||||||
flow_executor = self.get_shadow_root("ak-flow-executor")
|
flow_executor = self.get_shadow_root("ak-flow-executor")
|
||||||
validation_stage = self.get_shadow_root("ak-stage-authenticator-validate", 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 = 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(totp.token())
|
||||||
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER)
|
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER)
|
||||||
self.wait_for_url(self.if_user_url("/library"))
|
self.wait_for_url(self.if_user_url("/library"))
|
||||||
self.assert_user(self.user)
|
self.assert_user(self.user)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_stages_authenticator_totp", "0006_default_setup_flow")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
|
@apply_blueprint("blueprints/default/20-flow-default-authenticator-totp-setup.yaml")
|
||||||
def test_totp_setup(self):
|
def test_totp_setup(self):
|
||||||
"""test TOTP Setup stage"""
|
"""test TOTP Setup stage"""
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
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())
|
self.assertTrue(TOTPDevice.objects.filter(user=self.user, confirmed=True).exists())
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_stages_authenticator_static", "0005_default_setup_flow")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
|
@apply_blueprint("blueprints/default/20-flow-default-authenticator-static-setup.yaml")
|
||||||
def test_static_setup(self):
|
def test_static_setup(self):
|
||||||
"""test Static OTP Setup stage"""
|
"""test Static OTP Setup stage"""
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
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 import expected_conditions as ec
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.core.tests.utils import create_test_flow
|
from authentik.core.tests.utils import create_test_flow
|
||||||
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
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.prompt.models import FieldTypes, Prompt, PromptStage
|
||||||
from authentik.stages.user_login.models import UserLoginStage
|
from authentik.stages.user_login.models import UserLoginStage
|
||||||
from authentik.stages.user_write.models import UserWriteStage
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -39,8 +40,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_enroll_2_step(self):
|
def test_enroll_2_step(self):
|
||||||
"""Test 2-step enroll flow"""
|
"""Test 2-step enroll flow"""
|
||||||
# First stage fields
|
# First stage fields
|
||||||
|
@ -103,8 +106,10 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||||
self.assertEqual(user.email, "foo@bar.baz")
|
self.assertEqual(user.email, "foo@bar.baz")
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_enroll_email(self):
|
def test_enroll_email(self):
|
||||||
"""Test enroll with Email verification"""
|
"""Test enroll with Email verification"""
|
||||||
# First stage fields
|
# First stage fields
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
from sys import platform
|
from sys import platform
|
||||||
from unittest.case import skipUnless
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -10,8 +11,10 @@ class TestFlowsLogin(SeleniumTestCase):
|
||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
self.driver.get(
|
self.driver.get(
|
||||||
|
|
|
@ -5,11 +5,12 @@ from unittest.case import skipUnless
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.flows.models import Flow, FlowDesignation
|
from authentik.flows.models import Flow, FlowDesignation
|
||||||
from authentik.lib.generators import generate_key
|
from authentik.lib.generators import generate_key
|
||||||
from authentik.stages.password.models import PasswordStage
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -17,9 +18,11 @@ class TestFlowsStageSetup(SeleniumTestCase):
|
||||||
"""test stage setup flows"""
|
"""test stage setup flows"""
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint("blueprints/default/0-flow-password-change.yaml")
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_stages_password", "0002_passwordstage_change_flow")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_password_change(self):
|
def test_password_change(self):
|
||||||
"""test password change flow"""
|
"""test password change flow"""
|
||||||
# Ensure that password stage has change_flow set
|
# 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 import ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, SUBTREE, Connection, Server
|
||||||
from ldap3.core.exceptions import LDAPInvalidCredentialsResult
|
from ldap3.core.exceptions import LDAPInvalidCredentialsResult
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application, User
|
from authentik.core.models import Application, User
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.flows.models import Flow
|
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.outposts.models import Outpost, OutpostConfig, OutpostType
|
||||||
from authentik.providers.ldap.models import APIAccessMode, LDAPProvider
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -81,8 +82,10 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
return outpost
|
return outpost
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@object_manager
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_ldap_bind_success(self):
|
def test_ldap_bind_success(self):
|
||||||
"""Test simple bind"""
|
"""Test simple bind"""
|
||||||
self._prepare()
|
self._prepare()
|
||||||
|
@ -106,8 +109,10 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@object_manager
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_ldap_bind_success_ssl(self):
|
def test_ldap_bind_success_ssl(self):
|
||||||
"""Test simple bind with ssl"""
|
"""Test simple bind with ssl"""
|
||||||
self._prepare()
|
self._prepare()
|
||||||
|
@ -131,8 +136,10 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@object_manager
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
|
)
|
||||||
def test_ldap_bind_fail(self):
|
def test_ldap_bind_fail(self):
|
||||||
"""Test simple bind (failed)"""
|
"""Test simple bind (failed)"""
|
||||||
self._prepare()
|
self._prepare()
|
||||||
|
@ -154,8 +161,11 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@object_manager
|
"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):
|
def test_ldap_bind_search(self):
|
||||||
"""Test simple bind + search"""
|
"""Test simple bind + search"""
|
||||||
outpost = self._prepare()
|
outpost = self._prepare()
|
||||||
|
|
|
@ -8,13 +8,14 @@ from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
from authentik.policies.expression.models import ExpressionPolicy
|
from authentik.policies.expression.models import ExpressionPolicy
|
||||||
from authentik.policies.models import PolicyBinding
|
from authentik.policies.models import PolicyBinding
|
||||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -56,10 +57,18 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OAuth Provider flow (default authorization flow with implied consent)"""
|
"""test OAuth Provider flow (default authorization flow with implied consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -104,10 +113,18 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OAuth Provider flow (default authorization flow with explicit consent)"""
|
"""test OAuth Provider flow (default authorization flow with explicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -171,10 +188,15 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_denied(self):
|
||||||
"""test OAuth Provider flow (default authorization flow, denied)"""
|
"""test OAuth Provider flow (default authorization flow, denied)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
|
|
@ -8,6 +8,7 @@ from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.core.tests.utils import create_test_cert
|
from authentik.core.tests.utils import create_test_cert
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
|
@ -20,7 +21,7 @@ from authentik.providers.oauth2.constants import (
|
||||||
SCOPE_OPENID_PROFILE,
|
SCOPE_OPENID_PROFILE,
|
||||||
)
|
)
|
||||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -65,10 +66,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_redirect_uri_error(self):
|
||||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -106,11 +115,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -161,11 +177,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_logout(self):
|
||||||
"""test OpenID Provider flow with logout"""
|
"""test OpenID Provider flow with logout"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -225,11 +248,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
self.driver.find_element(By.ID, "logout").click()
|
self.driver.find_element(By.ID, "logout").click()
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -298,10 +328,18 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_authorization_denied(self):
|
||||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
|
@ -10,6 +10,7 @@ from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.core.tests.utils import create_test_cert
|
from authentik.core.tests.utils import create_test_cert
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
|
@ -22,7 +23,7 @@ from authentik.providers.oauth2.constants import (
|
||||||
SCOPE_OPENID_PROFILE,
|
SCOPE_OPENID_PROFILE,
|
||||||
)
|
)
|
||||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -64,10 +65,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_redirect_uri_error(self):
|
||||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -105,11 +111,16 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -155,11 +166,16 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
self.assertEqual(body["UserInfo"]["email"], self.user.email)
|
self.assertEqual(body["UserInfo"]["email"], self.user.email)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -220,10 +236,15 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||||
self.assertEqual(body["UserInfo"]["email"], self.user.email)
|
self.assertEqual(body["UserInfo"]["email"], self.user.email)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_authorization_denied(self):
|
||||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
|
@ -10,6 +10,7 @@ from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.core.tests.utils import create_test_cert
|
from authentik.core.tests.utils import create_test_cert
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
|
@ -22,7 +23,7 @@ from authentik.providers.oauth2.constants import (
|
||||||
SCOPE_OPENID_PROFILE,
|
SCOPE_OPENID_PROFILE,
|
||||||
)
|
)
|
||||||
from authentik.providers.oauth2.models import ClientTypes, OAuth2Provider, ScopeMapping
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -64,10 +65,15 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_redirect_uri_error(self):
|
||||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -105,11 +111,16 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -150,11 +161,16 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
self.assertEqual(body["profile"]["email"], self.user.email)
|
self.assertEqual(body["profile"]["email"], self.user.email)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
@ -211,10 +227,15 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||||
self.assertEqual(body["profile"]["email"], self.user.email)
|
self.assertEqual(body["profile"]["email"], self.user.email)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
|
@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):
|
def test_authorization_denied(self):
|
||||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
|
@ -11,12 +11,13 @@ from docker.models.containers import Container
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
from authentik import __version__
|
from authentik import __version__
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType
|
from authentik.outposts.models import DockerServiceConnection, Outpost, OutpostConfig, OutpostType
|
||||||
from authentik.outposts.tasks import outpost_local_connection
|
from authentik.outposts.tasks import outpost_local_connection
|
||||||
from authentik.providers.proxy.models import ProxyProvider
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -53,11 +54,19 @@ class TestProviderProxy(SeleniumTestCase):
|
||||||
return container
|
return container
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_proxy_simple(self):
|
||||||
"""Test simple outpost setup with single provider"""
|
"""Test simple outpost setup with single provider"""
|
||||||
# set additionalHeaders to test later
|
# set additionalHeaders to test later
|
||||||
|
@ -116,11 +125,15 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||||
"""Test Proxy connectivity over websockets"""
|
"""Test Proxy connectivity over websockets"""
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_proxy_connectivity(self):
|
||||||
"""Test proxy connectivity over websocket"""
|
"""Test proxy connectivity over websocket"""
|
||||||
outpost_local_connection()
|
outpost_local_connection()
|
||||||
|
|
|
@ -10,6 +10,7 @@ from docker.types import Healthcheck
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.core.tests.utils import create_test_cert
|
from authentik.core.tests.utils import create_test_cert
|
||||||
from authentik.flows.models import Flow
|
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.policies.models import PolicyBinding
|
||||||
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
|
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
|
||||||
from authentik.sources.saml.processors.constants import SAML_BINDING_POST
|
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")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
|
@ -63,11 +64,18 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_sp_initiated_implicit(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -125,11 +133,18 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_sp_initiated_explicit(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (explicit consent)"""
|
"""test SAML Provider flow SP-initiated flow (explicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -202,11 +217,18 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_sp_initiated_explicit_post(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (explicit consent) (POST binding)"""
|
"""test SAML Provider flow SP-initiated flow (explicit consent) (POST binding)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -279,11 +301,18 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_idp_initiated_implicit(self):
|
||||||
"""test SAML Provider flow IdP-initiated flow (implicit consent)"""
|
"""test SAML Provider flow IdP-initiated flow (implicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -347,11 +376,18 @@ class TestProviderSAML(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0010_provider_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_sp_initiated_denied(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (Policy denies access)"""
|
"""test SAML Provider flow SP-initiated flow (Policy denies access)"""
|
||||||
# Bootstrap all needed objects
|
# 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 selenium.webdriver.support.wait import WebDriverWait
|
||||||
from yaml import safe_dump
|
from yaml import safe_dump
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
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.types.manager import MANAGER, SourceType
|
||||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||||
from authentik.stages.identification.models import IdentificationStage
|
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
|
CONFIG_PATH = "/tmp/dex.yml" # nosec
|
||||||
|
|
||||||
|
@ -141,11 +142,19 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||||
ident_stage.save()
|
ident_stage.save()
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0009_source_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_oauth_enroll(self):
|
||||||
"""test OAuth Source With With OIDC"""
|
"""test OAuth Source With With OIDC"""
|
||||||
self.create_objects()
|
self.create_objects()
|
||||||
|
@ -190,11 +199,14 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||||
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0009_source_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_oauth_enroll_auth(self):
|
||||||
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
||||||
self.test_oauth_enroll()
|
self.test_oauth_enroll()
|
||||||
|
@ -279,11 +291,15 @@ class TestSourceOAuth1(SeleniumTestCase):
|
||||||
ident_stage.save()
|
ident_stage.save()
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0009_source_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@object_manager
|
@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):
|
def test_oauth_enroll(self):
|
||||||
"""test OAuth Source With With OIDC"""
|
"""test OAuth Source With With OIDC"""
|
||||||
self.create_objects()
|
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 import expected_conditions as ec
|
||||||
from selenium.webdriver.support.wait import WebDriverWait
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
|
|
||||||
|
from authentik.blueprints import apply_blueprint
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
|
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
|
||||||
from authentik.stages.identification.models import IdentificationStage
|
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-----
|
IDP_CERT = """-----BEGIN CERTIFICATE-----
|
||||||
MIIDXTCCAkWgAwIBAgIJALmVVuDWu4NYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
MIIDXTCCAkWgAwIBAgIJALmVVuDWu4NYMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||||
|
@ -94,12 +95,15 @@ class TestSourceSAML(SeleniumTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0009_source_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@apply_migration("authentik_sources_saml", "0010_samlsource_pre_authentication_flow")
|
@apply_blueprint(
|
||||||
@object_manager
|
"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):
|
def test_idp_redirect(self):
|
||||||
"""test SAML Source With redirect binding"""
|
"""test SAML Source With redirect binding"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -161,12 +165,15 @@ class TestSourceSAML(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0009_source_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@apply_migration("authentik_sources_saml", "0010_samlsource_pre_authentication_flow")
|
@apply_blueprint(
|
||||||
@object_manager
|
"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):
|
def test_idp_post(self):
|
||||||
"""test SAML Source With post binding"""
|
"""test SAML Source With post binding"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
@ -241,12 +248,15 @@ class TestSourceSAML(SeleniumTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
@retry()
|
@retry()
|
||||||
@apply_migration("authentik_flows", "0008_default_flows")
|
@apply_blueprint(
|
||||||
@apply_migration("authentik_flows", "0011_flow_title")
|
"blueprints/default/10-flow-default-authentication-flow.yaml",
|
||||||
@apply_migration("authentik_flows", "0009_source_flows")
|
"blueprints/default/10-flow-default-invalidation-flow.yaml",
|
||||||
@apply_migration("authentik_crypto", "0002_create_self_signed_kp")
|
)
|
||||||
@apply_migration("authentik_sources_saml", "0010_samlsource_pre_authentication_flow")
|
@apply_blueprint(
|
||||||
@object_manager
|
"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):
|
def test_idp_post_auto(self):
|
||||||
"""test SAML Source With post binding (auto redirect)"""
|
"""test SAML Source With post binding (auto redirect)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
|
|
@ -10,7 +10,6 @@ from django.apps import apps
|
||||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.migrations.loader import MigrationLoader
|
from django.db.migrations.loader import MigrationLoader
|
||||||
from django.db.migrations.operations.special import RunPython
|
|
||||||
from django.test.testcases import TransactionTestCase
|
from django.test.testcases import TransactionTestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from docker import DockerClient, from_env
|
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 selenium.webdriver.support.ui import WebDriverWait
|
||||||
from structlog.stdlib import get_logger
|
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.api.users import UserSerializer
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
|
@ -193,37 +192,22 @@ def get_loader():
|
||||||
return MigrationLoader(connection)
|
return MigrationLoader(connection)
|
||||||
|
|
||||||
|
|
||||||
def apply_migration(app_name: str, migration_name: str):
|
def reconcile_app(app_name: str):
|
||||||
"""Re-apply migrations that create objects using RunPython before test cases"""
|
"""Re-reconcile AppConfig methods"""
|
||||||
|
|
||||||
def wrapper_outter(func: Callable):
|
def wrapper_outer(func: Callable):
|
||||||
"""Retry test multiple times"""
|
"""Re-reconcile AppConfig methods"""
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(self: TransactionTestCase, *args, **kwargs):
|
def wrapper(self: TransactionTestCase, *args, **kwargs):
|
||||||
migration = get_loader().get_migration(app_name, migration_name)
|
config = apps.get_app_config(app_name)
|
||||||
with connection.schema_editor() as schema_editor:
|
if isinstance(config, ManagedAppConfig):
|
||||||
for operation in migration.operations:
|
config.reconcile()
|
||||||
if not isinstance(operation, RunPython):
|
|
||||||
continue
|
|
||||||
operation.code(apps, schema_editor)
|
|
||||||
return func(self, *args, **kwargs)
|
return func(self, *args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return wrapper_outter
|
return wrapper_outer
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def retry(max_retires=RETRIES, exceptions=None):
|
def retry(max_retires=RETRIES, exceptions=None):
|
||||||
|
|
|
@ -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
|
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
|
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`.
|
||||||
|
|
||||||
```yaml
|
|
||||||
debug: true
|
|
||||||
postgresql:
|
|
||||||
user: postgres
|
|
||||||
|
|
||||||
log_level: debug
|
|
||||||
secret_key: "A long key you can generate with `pwgen 40 1` for example"
|
|
||||||
```
|
|
||||||
|
|
||||||
To apply database migrations, run `make migrate`. This is needed after the initial setup, and whenever you fetch new source from upstream.
|
To apply database migrations, run `make migrate`. This is needed after the initial setup, and whenever you fetch new source from upstream.
|
||||||
|
|
||||||
|
|
Reference in New Issue