Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2025-06-04 16:17:15 +02:00
parent eb87e30076
commit f839aef33a
8 changed files with 48 additions and 52 deletions

View File

@ -1,6 +1,7 @@
"""authentik administration overview""" """authentik administration overview"""
from django.core.cache import cache from django.core.cache import cache
from django_tenants.utils import get_public_schema_name
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
from packaging.version import parse from packaging.version import parse
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
@ -13,6 +14,7 @@ from authentik import __version__, get_build_hash
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.outposts.models import Outpost from authentik.outposts.models import Outpost
from authentik.tenants.utils import get_current_tenant
class VersionSerializer(PassiveSerializer): class VersionSerializer(PassiveSerializer):
@ -35,6 +37,8 @@ class VersionSerializer(PassiveSerializer):
def get_version_latest(self, _) -> str: def get_version_latest(self, _) -> str:
"""Get latest version from cache""" """Get latest version from cache"""
if get_current_tenant().schema_name == get_public_schema_name():
return __version__
version_in_cache = cache.get(VERSION_CACHE_KEY) version_in_cache = cache.get(VERSION_CACHE_KEY)
if not version_in_cache: # pragma: no cover if not version_in_cache: # pragma: no cover
update_latest_version.send() update_latest_version.send()

View File

@ -17,17 +17,29 @@ class AuthentikAdminConfig(ManagedAppConfig):
verbose_name = "authentik Admin" verbose_name = "authentik Admin"
default = True default = True
@ManagedAppConfig.reconcile_tenant @ManagedAppConfig.reconcile_global
def clear_update_notifications(self): def clear_update_notifications(self):
from authentik.admin.tasks import clear_update_notifications """Clear update notifications on startup if the notification was for the version
we're running now."""
from packaging.version import parse
clear_update_notifications.send() from authentik.admin.tasks import LOCAL_VERSION
from authentik.events.models import EventAction, Notification
for notification in Notification.objects.filter(event__action=EventAction.UPDATE_AVAILABLE):
if "new_version" not in notification.event.context:
continue
notification_version = notification.event.context["new_version"]
if LOCAL_VERSION >= parse(notification_version):
notification.delete()
@property @property
def tenant_schedule_specs(self) -> list[ScheduleSpec]: def global_schedule_specs(self) -> list[ScheduleSpec]:
from authentik.admin.tasks import update_latest_version
return [ return [
ScheduleSpec( ScheduleSpec(
actor_name="authentik.admin.tasks.update_latest_version", actor_name=update_latest_version.actor_name,
crontab=f"{fqdn_rand('admin_latest_version')} * * * *", crontab=f"{fqdn_rand('admin_latest_version')} * * * *",
), ),
] ]

View File

@ -1,7 +1,6 @@
"""authentik admin tasks""" """authentik admin tasks"""
from django.core.cache import cache from django.core.cache import cache
from django.db import DatabaseError, InternalError, ProgrammingError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dramatiq import actor from dramatiq import actor
from packaging.version import parse from packaging.version import parse
@ -10,7 +9,7 @@ from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash from authentik import __version__, get_build_hash
from authentik.admin.apps import PROM_INFO from authentik.admin.apps import PROM_INFO
from authentik.events.models import Event, EventAction, Notification from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.utils.http import get_http_session from authentik.lib.utils.http import get_http_session
from authentik.tasks.middleware import CurrentTask from authentik.tasks.middleware import CurrentTask
@ -33,18 +32,6 @@ def _set_prom_info():
) )
@actor(queue_name="startup", throws=(DatabaseError, ProgrammingError, InternalError))
def clear_update_notifications():
"""Clear update notifications on startup if the notification was for the version
we're running now."""
for notification in Notification.objects.filter(event__action=EventAction.UPDATE_AVAILABLE):
if "new_version" not in notification.event.context:
continue
notification_version = notification.event.context["new_version"]
if LOCAL_VERSION >= parse(notification_version):
notification.delete()
@actor @actor
def update_latest_version(): def update_latest_version():
"""Update latest version info""" """Update latest version info"""

View File

@ -6,6 +6,7 @@ from inspect import ismethod
from django.apps import AppConfig from django.apps import AppConfig
from django.db import DatabaseError, InternalError, ProgrammingError from django.db import DatabaseError, InternalError, ProgrammingError
from dramatiq.actor import Actor
from dramatiq.broker import get_broker from dramatiq.broker import get_broker
from structlog.stdlib import BoundLogger, get_logger from structlog.stdlib import BoundLogger, get_logger
@ -93,6 +94,18 @@ class ManagedAppConfig(AppConfig):
"""Get a list of schedule specs that must exist in the default tenant""" """Get a list of schedule specs that must exist in the default tenant"""
return [] return []
@property
def tenant_startup_tasks(self) -> list[Actor]:
"""Get a list of actors to dispatch on startup in each tenant.
If an associated schedule is found and is paused, the actor is skipped."""
return []
@property
def global_startup_tasks(self) -> list[Actor]:
"""Get a list of actors to dispatch on startup in the default tenant.
If an associated schedule is found and is paused, the actor is skipped."""
return []
def _reconcile_tenant(self) -> None: def _reconcile_tenant(self) -> None:
"""reconcile ourselves for tenanted methods""" """reconcile ourselves for tenanted methods"""
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
@ -125,40 +138,27 @@ class AuthentikBlueprintsConfig(ManagedAppConfig):
verbose_name = "authentik Blueprints" verbose_name = "authentik Blueprints"
default = True default = True
def import_models(self):
super().import_models()
self.import_module("authentik.blueprints.v1.meta.apply_blueprint")
@ManagedAppConfig.reconcile_global @ManagedAppConfig.reconcile_global
def tasks_middlewares(self): def tasks_middlewares(self):
from authentik.blueprints.v1.tasks import BlueprintWatcherMiddleware from authentik.blueprints.v1.tasks import BlueprintWatcherMiddleware
get_broker().add_middleware(BlueprintWatcherMiddleware()) get_broker().add_middleware(BlueprintWatcherMiddleware())
@ManagedAppConfig.reconcile_tenant
def blueprints_discovery(self):
"""Run blueprint discovery"""
from authentik.tasks.schedules.models import Schedule
from authentik.blueprints.v1.tasks import blueprints_discovery, clear_failed_blueprints
for schedule in Schedule.objects.filter(
actor_name__in=(
blueprints_discovery.actor_name,
clear_failed_blueprints.actor_name,
),
paused=False,
):
schedule.send()
def import_models(self):
super().import_models()
self.import_module("authentik.blueprints.v1.meta.apply_blueprint")
@property @property
def tenant_schedule_specs(self) -> list[ScheduleSpec]: def tenant_schedule_specs(self) -> list[ScheduleSpec]:
return [ return [
ScheduleSpec( ScheduleSpec(
actor_name="authentik.blueprints.v1.tasks.blueprints_discovery", actor_name="authentik.blueprints.v1.tasks.blueprints_discovery",
crontab=f"{fqdn_rand('blueprints_v1_discover')} * * * *", crontab=f"{fqdn_rand('blueprints_v1_discover')} * * * *",
run_on_startup=True,
), ),
ScheduleSpec( ScheduleSpec(
actor_name="authentik.blueprints.v1.tasks.clear_failed_blueprints", actor_name="authentik.blueprints.v1.tasks.clear_failed_blueprints",
crontab=f"{fqdn_rand('blueprints_v1_cleanup')} * * * *", crontab=f"{fqdn_rand('blueprints_v1_cleanup')} * * * *",
run_on_startup=True,
), ),
] ]

View File

@ -55,6 +55,7 @@ class AuthentikOutpostConfig(ManagedAppConfig):
managed=MANAGED_OUTPOST, managed=MANAGED_OUTPOST,
) )
if created: if created:
outpost.set_oauth_defaults()
if KubernetesServiceConnection.objects.exists(): if KubernetesServiceConnection.objects.exists():
outpost.service_connection = KubernetesServiceConnection.objects.first() outpost.service_connection = KubernetesServiceConnection.objects.first()
elif DockerServiceConnection.objects.exists(): elif DockerServiceConnection.objects.exists():

View File

@ -13,6 +13,8 @@ class AuthentikProviderProxyConfig(ManagedAppConfig):
@ManagedAppConfig.reconcile_tenant @ManagedAppConfig.reconcile_tenant
def proxy_set_defaults(self): def proxy_set_defaults(self):
from authentik.providers.proxy.tasks import proxy_set_defaults from authentik.providers.proxy.models import ProxyProvider
proxy_set_defaults.send() for provider in ProxyProvider.objects.all():
provider.set_oauth_defaults()
provider.save()

View File

@ -2,23 +2,11 @@
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.db import DatabaseError, InternalError, ProgrammingError
from dramatiq.actor import actor from dramatiq.actor import actor
from authentik.outposts.consumer import OUTPOST_GROUP from authentik.outposts.consumer import OUTPOST_GROUP
from authentik.outposts.models import Outpost, OutpostType from authentik.outposts.models import Outpost, OutpostType
from authentik.providers.oauth2.id_token import hash_session_key from authentik.providers.oauth2.id_token import hash_session_key
from authentik.providers.proxy.models import ProxyProvider
@actor(
throws=(DatabaseError, ProgrammingError, InternalError),
)
def proxy_set_defaults():
"""Ensure correct defaults are set for all providers"""
for provider in ProxyProvider.objects.all():
provider.set_oauth_defaults()
provider.save()
@actor @actor

View File

@ -23,6 +23,8 @@ class ScheduleSpec:
send_on_save: bool = False send_on_save: bool = False
run_on_startup: bool = False
def get_uid(self) -> str: def get_uid(self) -> str:
if self.uid is not None: if self.uid is not None:
return f"{self.actor_name}:{self.uid}" return f"{self.actor_name}:{self.uid}"