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"""
from django.core.cache import cache
from django_tenants.utils import get_public_schema_name
from drf_spectacular.utils import extend_schema
from packaging.version import parse
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.core.api.utils import PassiveSerializer
from authentik.outposts.models import Outpost
from authentik.tenants.utils import get_current_tenant
class VersionSerializer(PassiveSerializer):
@ -35,6 +37,8 @@ class VersionSerializer(PassiveSerializer):
def get_version_latest(self, _) -> str:
"""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)
if not version_in_cache: # pragma: no cover
update_latest_version.send()

View File

@ -17,17 +17,29 @@ class AuthentikAdminConfig(ManagedAppConfig):
verbose_name = "authentik Admin"
default = True
@ManagedAppConfig.reconcile_tenant
@ManagedAppConfig.reconcile_global
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
def tenant_schedule_specs(self) -> list[ScheduleSpec]:
def global_schedule_specs(self) -> list[ScheduleSpec]:
from authentik.admin.tasks import update_latest_version
return [
ScheduleSpec(
actor_name="authentik.admin.tasks.update_latest_version",
actor_name=update_latest_version.actor_name,
crontab=f"{fqdn_rand('admin_latest_version')} * * * *",
),
]

View File

@ -1,7 +1,6 @@
"""authentik admin tasks"""
from django.core.cache import cache
from django.db import DatabaseError, InternalError, ProgrammingError
from django.utils.translation import gettext_lazy as _
from dramatiq import actor
from packaging.version import parse
@ -10,7 +9,7 @@ from structlog.stdlib import get_logger
from authentik import __version__, get_build_hash
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.utils.http import get_http_session
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
def update_latest_version():
"""Update latest version info"""

View File

@ -6,6 +6,7 @@ from inspect import ismethod
from django.apps import AppConfig
from django.db import DatabaseError, InternalError, ProgrammingError
from dramatiq.actor import Actor
from dramatiq.broker import get_broker
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"""
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:
"""reconcile ourselves for tenanted methods"""
from authentik.tenants.models import Tenant
@ -125,40 +138,27 @@ class AuthentikBlueprintsConfig(ManagedAppConfig):
verbose_name = "authentik Blueprints"
default = True
def import_models(self):
super().import_models()
self.import_module("authentik.blueprints.v1.meta.apply_blueprint")
@ManagedAppConfig.reconcile_global
def tasks_middlewares(self):
from authentik.blueprints.v1.tasks import 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
def tenant_schedule_specs(self) -> list[ScheduleSpec]:
return [
ScheduleSpec(
actor_name="authentik.blueprints.v1.tasks.blueprints_discovery",
crontab=f"{fqdn_rand('blueprints_v1_discover')} * * * *",
run_on_startup=True,
),
ScheduleSpec(
actor_name="authentik.blueprints.v1.tasks.clear_failed_blueprints",
crontab=f"{fqdn_rand('blueprints_v1_cleanup')} * * * *",
run_on_startup=True,
),
]

View File

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

View File

@ -13,6 +13,8 @@ class AuthentikProviderProxyConfig(ManagedAppConfig):
@ManagedAppConfig.reconcile_tenant
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 channels.layers import get_channel_layer
from django.db import DatabaseError, InternalError, ProgrammingError
from dramatiq.actor import actor
from authentik.outposts.consumer import OUTPOST_GROUP
from authentik.outposts.models import Outpost, OutpostType
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

View File

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