From e89659fe71ed68860050cfcf3d175d4c1ed501dd Mon Sep 17 00:00:00 2001 From: Marc 'risson' Schmitt Date: Wed, 2 Apr 2025 17:52:10 +0200 Subject: [PATCH] wip Signed-off-by: Marc 'risson' Schmitt --- authentik/api/apps.py | 7 +++--- authentik/blueprints/apps.py | 19 ++++++++------- authentik/blueprints/tasks.py | 2 ++ authentik/blueprints/v1/tasks.py | 2 +- authentik/brands/apps.py | 5 ++-- authentik/outposts/models.py | 2 +- authentik/outposts/tasks.py | 5 ++-- authentik/policies/dummy/apps.py | 5 ++-- authentik/policies/event_matcher/apps.py | 5 ++-- authentik/policies/expiry/apps.py | 5 ++-- authentik/policies/expression/apps.py | 5 ++-- authentik/policies/geoip/apps.py | 5 ++-- authentik/policies/password/apps.py | 5 ++-- authentik/providers/ldap/apps.py | 5 ++-- authentik/providers/radius/apps.py | 5 ++-- authentik/providers/saml/apps.py | 5 ++-- authentik/recovery/apps.py | 5 ++-- authentik/sources/plex/apps.py | 5 ++-- authentik/stages/authenticator/apps.py | 5 ++-- authentik/stages/authenticator_sms/apps.py | 5 ++-- authentik/stages/authenticator_totp/apps.py | 5 ++-- .../stages/authenticator_validate/apps.py | 5 ++-- authentik/stages/captcha/apps.py | 5 ++-- authentik/stages/consent/apps.py | 5 ++-- authentik/stages/deny/apps.py | 5 ++-- authentik/stages/dummy/apps.py | 5 ++-- authentik/stages/identification/apps.py | 5 ++-- authentik/stages/invitation/apps.py | 5 ++-- authentik/stages/password/apps.py | 5 ++-- authentik/stages/prompt/apps.py | 5 ++-- authentik/stages/redirect/apps.py | 5 ++-- authentik/stages/user_delete/apps.py | 5 ++-- authentik/stages/user_login/apps.py | 5 ++-- authentik/stages/user_logout/apps.py | 5 ++-- authentik/stages/user_write/apps.py | 5 ++-- authentik/tasks/apps.py | 1 + authentik/tasks/broker.py | 23 ++++++++++++------ authentik/tasks/management/commands/worker.py | 3 ++- authentik/tasks/schedules/api.py | 4 ++-- authentik/tasks/schedules/apps.py | 24 +++++++++++++++++++ authentik/tasks/setup.py | 24 ++++++------------- internal/web/web.go | 7 ++++++ internal/worker/worker.go | 2 +- manage.py | 1 + 44 files changed, 169 insertions(+), 102 deletions(-) create mode 100644 authentik/blueprints/tasks.py diff --git a/authentik/api/apps.py b/authentik/api/apps.py index 8ae859a54d..a6adad91bc 100644 --- a/authentik/api/apps.py +++ b/authentik/api/apps.py @@ -1,12 +1,13 @@ """authentik API AppConfig""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikAPIConfig(AppConfig): +class AuthentikAPIConfig(ManagedAppConfig): """authentik API Config""" name = "authentik.api" label = "authentik_api" - mountpoint = "api/" verbose_name = "authentik API" + default = True + mountpoint = "api/" diff --git a/authentik/blueprints/apps.py b/authentik/blueprints/apps.py index 054441947c..20fe396c2d 100644 --- a/authentik/blueprints/apps.py +++ b/authentik/blueprints/apps.py @@ -6,6 +6,7 @@ from inspect import ismethod from django.apps import AppConfig from django.db import DatabaseError, InternalError, ProgrammingError +from dramatiq.broker import get_broker from structlog.stdlib import BoundLogger, get_logger from authentik.lib.utils.time import fqdn_rand @@ -92,10 +93,6 @@ class ManagedAppConfig(AppConfig): """Get a list of schedule specs that must exist in the default tenant""" return [] - def _reconcile_schedules(self, schedules: list[ScheduleSpec]): - for schedule in schedules: - schedule.update_or_create() - def _reconcile_tenant(self) -> None: """reconcile ourselves for tenanted methods""" from authentik.tenants.models import Tenant @@ -108,7 +105,6 @@ class ManagedAppConfig(AppConfig): for tenant in tenants: with tenant: self._reconcile(self.RECONCILE_TENANT_CATEGORY) - self._reconcile_schedules(self.tenant_schedule_specs) def _reconcile_global(self) -> None: """ @@ -119,7 +115,6 @@ class ManagedAppConfig(AppConfig): with schema_context(get_public_schema_name()): self._reconcile(self.RECONCILE_GLOBAL_CATEGORY) - self._reconcile_schedules(self.global_schedule_specs) class AuthentikBlueprintsConfig(ManagedAppConfig): @@ -131,9 +126,15 @@ class AuthentikBlueprintsConfig(ManagedAppConfig): default = True @ManagedAppConfig.reconcile_global - def load_blueprints_v1_tasks(self): - """Load v1 tasks""" - self.import_module("authentik.blueprints.v1.tasks") + def tasks_middlewares(self): + from authentik.blueprints.v1.tasks import BlueprintWatcherMiddleware + + get_broker().add_middleware(BlueprintWatcherMiddleware()) + + # @ManagedAppConfig.reconcile_global + # def load_blueprints_v1_tasks(self): + # """Load v1 tasks""" + # self.import_module("authentik.blueprints.v1.tasks") @ManagedAppConfig.reconcile_tenant def blueprints_discovery(self): diff --git a/authentik/blueprints/tasks.py b/authentik/blueprints/tasks.py new file mode 100644 index 0000000000..6a90f77db5 --- /dev/null +++ b/authentik/blueprints/tasks.py @@ -0,0 +1,2 @@ +# Import all v1 tasks for auto task discovery +from authentik.blueprints.v1.tasks import * # noqa: F403 diff --git a/authentik/blueprints/v1/tasks.py b/authentik/blueprints/v1/tasks.py index 988f3422b0..78e1f5c1ab 100644 --- a/authentik/blueprints/v1/tasks.py +++ b/authentik/blueprints/v1/tasks.py @@ -65,7 +65,7 @@ class BlueprintWatcherMiddleware(Middleware): ) observer.start() - def before_worker_boot(self, broker, worker): + def after_worker_boot(self, broker, worker): self.start_blueprint_watcher() diff --git a/authentik/brands/apps.py b/authentik/brands/apps.py index ce9681396a..adbe9f5ef3 100644 --- a/authentik/brands/apps.py +++ b/authentik/brands/apps.py @@ -1,14 +1,15 @@ """authentik brands app""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikBrandsConfig(AppConfig): +class AuthentikBrandsConfig(ManagedAppConfig): """authentik Brand app""" name = "authentik.brands" label = "authentik_brands" verbose_name = "authentik Brands" + default = True mountpoints = { "authentik.brands.urls_root": "", } diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index f21f18192a..a359643ea7 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -317,7 +317,7 @@ class Outpost(ScheduledModel, SerializerModel, ManagedModel): ScheduleSpec( actor_name="authentik.outposts.tasks.outpost_controller", uid=self.pk, - args=(self.pk, "up"), + args=(self.pk,), kwargs={"action": "up", "from_cache": False}, crontab=f"{fqdn_rand('outpost_controller')} */4 * * *", description=_( diff --git a/authentik/outposts/tasks.py b/authentik/outposts/tasks.py index 85aedb25cd..b4da611feb 100644 --- a/authentik/outposts/tasks.py +++ b/authentik/outposts/tasks.py @@ -134,8 +134,9 @@ def outpost_controller(outpost_pk: str, action: str = "up", from_cache: bool = F @actor def outpost_token_ensurer(): - """Periodically ensure that all Outposts have valid Service Accounts - and Tokens""" + """ + Periodically ensure that all Outposts have valid Service Accounts and Tokens + """ self: Task = CurrentTask.get_task() all_outposts = Outpost.objects.all() for outpost in all_outposts: diff --git a/authentik/policies/dummy/apps.py b/authentik/policies/dummy/apps.py index 32792df9a4..c136de0a44 100644 --- a/authentik/policies/dummy/apps.py +++ b/authentik/policies/dummy/apps.py @@ -1,11 +1,12 @@ """Authentik policy dummy app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikPolicyDummyConfig(AppConfig): +class AuthentikPolicyDummyConfig(ManagedAppConfig): """Authentik policy_dummy app config""" name = "authentik.policies.dummy" label = "authentik_policies_dummy" verbose_name = "authentik Policies.Dummy" + default = True diff --git a/authentik/policies/event_matcher/apps.py b/authentik/policies/event_matcher/apps.py index 00a94c3a35..57ae14cbff 100644 --- a/authentik/policies/event_matcher/apps.py +++ b/authentik/policies/event_matcher/apps.py @@ -1,11 +1,12 @@ """authentik Event Matcher policy app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikPoliciesEventMatcherConfig(AppConfig): +class AuthentikPoliciesEventMatcherConfig(ManagedAppConfig): """authentik Event Matcher policy app config""" name = "authentik.policies.event_matcher" label = "authentik_policies_event_matcher" verbose_name = "authentik Policies.Event Matcher" + default = True diff --git a/authentik/policies/expiry/apps.py b/authentik/policies/expiry/apps.py index db29f9fcee..9c7ff3aad3 100644 --- a/authentik/policies/expiry/apps.py +++ b/authentik/policies/expiry/apps.py @@ -1,11 +1,12 @@ """Authentik policy_expiry app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikPolicyExpiryConfig(AppConfig): +class AuthentikPolicyExpiryConfig(ManagedAppConfig): """Authentik policy_expiry app config""" name = "authentik.policies.expiry" label = "authentik_policies_expiry" verbose_name = "authentik Policies.Expiry" + default = True diff --git a/authentik/policies/expression/apps.py b/authentik/policies/expression/apps.py index de7df61f90..44a614841a 100644 --- a/authentik/policies/expression/apps.py +++ b/authentik/policies/expression/apps.py @@ -1,11 +1,12 @@ """Authentik policy_expression app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikPolicyExpressionConfig(AppConfig): +class AuthentikPolicyExpressionConfig(ManagedAppConfig): """Authentik policy_expression app config""" name = "authentik.policies.expression" label = "authentik_policies_expression" verbose_name = "authentik Policies.Expression" + default = True diff --git a/authentik/policies/geoip/apps.py b/authentik/policies/geoip/apps.py index ae64d29d4e..c6d62a5e90 100644 --- a/authentik/policies/geoip/apps.py +++ b/authentik/policies/geoip/apps.py @@ -1,11 +1,12 @@ """Authentik policy geoip app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikPolicyGeoIPConfig(AppConfig): +class AuthentikPolicyGeoIPConfig(ManagedAppConfig): """Authentik policy_geoip app config""" name = "authentik.policies.geoip" label = "authentik_policies_geoip" verbose_name = "authentik Policies.GeoIP" + default = True diff --git a/authentik/policies/password/apps.py b/authentik/policies/password/apps.py index 7125647d79..bad9df2589 100644 --- a/authentik/policies/password/apps.py +++ b/authentik/policies/password/apps.py @@ -1,11 +1,12 @@ """authentik Password policy app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikPoliciesPasswordConfig(AppConfig): +class AuthentikPoliciesPasswordConfig(ManagedAppConfig): """authentik Password policy app config""" name = "authentik.policies.password" label = "authentik_policies_password" verbose_name = "authentik Policies.Password" + default = True diff --git a/authentik/providers/ldap/apps.py b/authentik/providers/ldap/apps.py index ffd5d75310..69887fb2ed 100644 --- a/authentik/providers/ldap/apps.py +++ b/authentik/providers/ldap/apps.py @@ -1,11 +1,12 @@ """authentik ldap provider app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikProviderLDAPConfig(AppConfig): +class AuthentikProviderLDAPConfig(ManagedAppConfig): """authentik ldap provider app config""" name = "authentik.providers.ldap" label = "authentik_providers_ldap" verbose_name = "authentik Providers.LDAP" + default = True diff --git a/authentik/providers/radius/apps.py b/authentik/providers/radius/apps.py index ac84219117..df5681c665 100644 --- a/authentik/providers/radius/apps.py +++ b/authentik/providers/radius/apps.py @@ -1,11 +1,12 @@ """authentik radius provider app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikProviderRadiusConfig(AppConfig): +class AuthentikProviderRadiusConfig(ManagedAppConfig): """authentik radius provider app config""" name = "authentik.providers.radius" label = "authentik_providers_radius" verbose_name = "authentik Providers.Radius" + default = True diff --git a/authentik/providers/saml/apps.py b/authentik/providers/saml/apps.py index 1d6d9c5ed6..147f770864 100644 --- a/authentik/providers/saml/apps.py +++ b/authentik/providers/saml/apps.py @@ -1,12 +1,13 @@ """authentik SAML IdP app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikProviderSAMLConfig(AppConfig): +class AuthentikProviderSAMLConfig(ManagedAppConfig): """authentik SAML IdP app config""" name = "authentik.providers.saml" label = "authentik_providers_saml" verbose_name = "authentik Providers.SAML" mountpoint = "application/saml/" + default = True diff --git a/authentik/recovery/apps.py b/authentik/recovery/apps.py index 85bbfa7b71..077706e165 100644 --- a/authentik/recovery/apps.py +++ b/authentik/recovery/apps.py @@ -1,12 +1,13 @@ """authentik Recovery app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikRecoveryConfig(AppConfig): +class AuthentikRecoveryConfig(ManagedAppConfig): """authentik Recovery app config""" name = "authentik.recovery" label = "authentik_recovery" verbose_name = "authentik Recovery" mountpoint = "recovery/" + default = True diff --git a/authentik/sources/plex/apps.py b/authentik/sources/plex/apps.py index 2170a61dc8..47e59ab8b0 100644 --- a/authentik/sources/plex/apps.py +++ b/authentik/sources/plex/apps.py @@ -1,11 +1,12 @@ """authentik plex config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikSourcePlexConfig(AppConfig): +class AuthentikSourcePlexConfig(ManagedAppConfig): """authentik source plex config""" name = "authentik.sources.plex" label = "authentik_sources_plex" verbose_name = "authentik Sources.Plex" + default = True diff --git a/authentik/stages/authenticator/apps.py b/authentik/stages/authenticator/apps.py index dd33812bbf..58c67e9409 100644 --- a/authentik/stages/authenticator/apps.py +++ b/authentik/stages/authenticator/apps.py @@ -1,11 +1,12 @@ """Authenticator""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageAuthenticatorConfig(AppConfig): +class AuthentikStageAuthenticatorConfig(ManagedAppConfig): """Authenticator App config""" name = "authentik.stages.authenticator" label = "authentik_stages_authenticator" verbose_name = "authentik Stages.Authenticator" + default = True diff --git a/authentik/stages/authenticator_sms/apps.py b/authentik/stages/authenticator_sms/apps.py index d5d6f749ee..4a14249dfe 100644 --- a/authentik/stages/authenticator_sms/apps.py +++ b/authentik/stages/authenticator_sms/apps.py @@ -1,11 +1,12 @@ """SMS""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageAuthenticatorSMSConfig(AppConfig): +class AuthentikStageAuthenticatorSMSConfig(ManagedAppConfig): """SMS App config""" name = "authentik.stages.authenticator_sms" label = "authentik_stages_authenticator_sms" verbose_name = "authentik Stages.Authenticator.SMS" + default = True diff --git a/authentik/stages/authenticator_totp/apps.py b/authentik/stages/authenticator_totp/apps.py index 7e624a95dd..9818b87215 100644 --- a/authentik/stages/authenticator_totp/apps.py +++ b/authentik/stages/authenticator_totp/apps.py @@ -1,11 +1,12 @@ """TOTP""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageAuthenticatorTOTPConfig(AppConfig): +class AuthentikStageAuthenticatorTOTPConfig(ManagedAppConfig): """TOTP App config""" name = "authentik.stages.authenticator_totp" label = "authentik_stages_authenticator_totp" verbose_name = "authentik Stages.Authenticator.TOTP" + default = True diff --git a/authentik/stages/authenticator_validate/apps.py b/authentik/stages/authenticator_validate/apps.py index 922440f955..143c8bc0dc 100644 --- a/authentik/stages/authenticator_validate/apps.py +++ b/authentik/stages/authenticator_validate/apps.py @@ -1,11 +1,12 @@ """Authenticator Validation Stage""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageAuthenticatorValidateConfig(AppConfig): +class AuthentikStageAuthenticatorValidateConfig(ManagedAppConfig): """Authenticator Validation Stage""" name = "authentik.stages.authenticator_validate" label = "authentik_stages_authenticator_validate" verbose_name = "authentik Stages.Authenticator.Validate" + default = True diff --git a/authentik/stages/captcha/apps.py b/authentik/stages/captcha/apps.py index 26c454ec3f..8383e78ac1 100644 --- a/authentik/stages/captcha/apps.py +++ b/authentik/stages/captcha/apps.py @@ -1,11 +1,12 @@ """authentik captcha app""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageCaptchaConfig(AppConfig): +class AuthentikStageCaptchaConfig(ManagedAppConfig): """authentik captcha app""" name = "authentik.stages.captcha" label = "authentik_stages_captcha" verbose_name = "authentik Stages.Captcha" + default = True diff --git a/authentik/stages/consent/apps.py b/authentik/stages/consent/apps.py index 0c0e664fb7..549ad6b322 100644 --- a/authentik/stages/consent/apps.py +++ b/authentik/stages/consent/apps.py @@ -1,11 +1,12 @@ """authentik consent app""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageConsentConfig(AppConfig): +class AuthentikStageConsentConfig(ManagedAppConfig): """authentik consent app""" name = "authentik.stages.consent" label = "authentik_stages_consent" verbose_name = "authentik Stages.Consent" + default = True diff --git a/authentik/stages/deny/apps.py b/authentik/stages/deny/apps.py index 4b1e4a1709..bd03f5fccc 100644 --- a/authentik/stages/deny/apps.py +++ b/authentik/stages/deny/apps.py @@ -1,11 +1,12 @@ """authentik deny stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageDenyConfig(AppConfig): +class AuthentikStageDenyConfig(ManagedAppConfig): """authentik deny stage config""" name = "authentik.stages.deny" label = "authentik_stages_deny" verbose_name = "authentik Stages.Deny" + default = True diff --git a/authentik/stages/dummy/apps.py b/authentik/stages/dummy/apps.py index 35f93e88c7..8ad8a6783a 100644 --- a/authentik/stages/dummy/apps.py +++ b/authentik/stages/dummy/apps.py @@ -1,11 +1,12 @@ """authentik dummy stage config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageDummyConfig(AppConfig): +class AuthentikStageDummyConfig(ManagedAppConfig): """authentik dummy stage config""" name = "authentik.stages.dummy" label = "authentik_stages_dummy" verbose_name = "authentik Stages.Dummy" + default = True diff --git a/authentik/stages/identification/apps.py b/authentik/stages/identification/apps.py index 184a52af77..d52c9c6310 100644 --- a/authentik/stages/identification/apps.py +++ b/authentik/stages/identification/apps.py @@ -1,11 +1,12 @@ """authentik identification stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageIdentificationConfig(AppConfig): +class AuthentikStageIdentificationConfig(ManagedAppConfig): """authentik identification stage config""" name = "authentik.stages.identification" label = "authentik_stages_identification" verbose_name = "authentik Stages.Identification" + default = True diff --git a/authentik/stages/invitation/apps.py b/authentik/stages/invitation/apps.py index db3b53d787..389cb3d45b 100644 --- a/authentik/stages/invitation/apps.py +++ b/authentik/stages/invitation/apps.py @@ -1,11 +1,12 @@ """authentik invitation stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageInvitationConfig(AppConfig): +class AuthentikStageInvitationConfig(ManagedAppConfig): """authentik invitation stage config""" name = "authentik.stages.invitation" label = "authentik_stages_invitation" verbose_name = "authentik Stages.Invitation" + default = True diff --git a/authentik/stages/password/apps.py b/authentik/stages/password/apps.py index d8231d7b72..674c66030c 100644 --- a/authentik/stages/password/apps.py +++ b/authentik/stages/password/apps.py @@ -1,11 +1,12 @@ """authentik core app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStagePasswordConfig(AppConfig): +class AuthentikStagePasswordConfig(ManagedAppConfig): """authentik password stage config""" name = "authentik.stages.password" label = "authentik_stages_password" verbose_name = "authentik Stages.Password" + default = True diff --git a/authentik/stages/prompt/apps.py b/authentik/stages/prompt/apps.py index d2f794ef76..5645f9e586 100644 --- a/authentik/stages/prompt/apps.py +++ b/authentik/stages/prompt/apps.py @@ -1,11 +1,12 @@ """authentik prompt stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStagePromptConfig(AppConfig): +class AuthentikStagePromptConfig(ManagedAppConfig): """authentik prompt stage config""" name = "authentik.stages.prompt" label = "authentik_stages_prompt" verbose_name = "authentik Stages.Prompt" + default = True diff --git a/authentik/stages/redirect/apps.py b/authentik/stages/redirect/apps.py index 782aab69b3..2b45267d14 100644 --- a/authentik/stages/redirect/apps.py +++ b/authentik/stages/redirect/apps.py @@ -1,11 +1,12 @@ """authentik redirect app""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageRedirectConfig(AppConfig): +class AuthentikStageRedirectConfig(ManagedAppConfig): """authentik redirect app""" name = "authentik.stages.redirect" label = "authentik_stages_redirect" verbose_name = "authentik Stages.Redirect" + default = True diff --git a/authentik/stages/user_delete/apps.py b/authentik/stages/user_delete/apps.py index e73dbfaab9..b6342c6aad 100644 --- a/authentik/stages/user_delete/apps.py +++ b/authentik/stages/user_delete/apps.py @@ -1,11 +1,12 @@ """authentik delete stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageUserDeleteConfig(AppConfig): +class AuthentikStageUserDeleteConfig(ManagedAppConfig): """authentik delete stage config""" name = "authentik.stages.user_delete" label = "authentik_stages_user_delete" verbose_name = "authentik Stages.User Delete" + default = True diff --git a/authentik/stages/user_login/apps.py b/authentik/stages/user_login/apps.py index a8a65ef4fe..2f8f3fad13 100644 --- a/authentik/stages/user_login/apps.py +++ b/authentik/stages/user_login/apps.py @@ -1,11 +1,12 @@ """authentik login stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageUserLoginConfig(AppConfig): +class AuthentikStageUserLoginConfig(ManagedAppConfig): """authentik login stage config""" name = "authentik.stages.user_login" label = "authentik_stages_user_login" verbose_name = "authentik Stages.User Login" + default = True diff --git a/authentik/stages/user_logout/apps.py b/authentik/stages/user_logout/apps.py index 1388728a58..cea866fc5c 100644 --- a/authentik/stages/user_logout/apps.py +++ b/authentik/stages/user_logout/apps.py @@ -1,11 +1,12 @@ """authentik logout stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageUserLogoutConfig(AppConfig): +class AuthentikStageUserLogoutConfig(ManagedAppConfig): """authentik logout stage config""" name = "authentik.stages.user_logout" label = "authentik_stages_user_logout" verbose_name = "authentik Stages.User Logout" + default = True diff --git a/authentik/stages/user_write/apps.py b/authentik/stages/user_write/apps.py index c5cbb244c5..6122c23b19 100644 --- a/authentik/stages/user_write/apps.py +++ b/authentik/stages/user_write/apps.py @@ -1,11 +1,12 @@ """authentik write stage app config""" -from django.apps import AppConfig +from authentik.blueprints.apps import ManagedAppConfig -class AuthentikStageUserWriteConfig(AppConfig): +class AuthentikStageUserWriteConfig(ManagedAppConfig): """authentik write stage config""" name = "authentik.stages.user_write" label = "authentik_stages_user_write" verbose_name = "authentik Stages.User Write" + default = True diff --git a/authentik/tasks/apps.py b/authentik/tasks/apps.py index 61a7b1c706..11b1fc6ba9 100644 --- a/authentik/tasks/apps.py +++ b/authentik/tasks/apps.py @@ -35,6 +35,7 @@ class AuthentikTasksConfig(ManagedAppConfig): broker.add_middleware(Pipelines()) broker.add_middleware(Retries(max_retries=max_retries)) broker.add_middleware(Results(backend=PostgresBackend(), store_results=True)) + broker.add_middleware(FullyQualifiedActorName()) broker.add_middleware(CurrentTask()) diff --git a/authentik/tasks/broker.py b/authentik/tasks/broker.py index f1c265976d..3e2654ac83 100644 --- a/authentik/tasks/broker.py +++ b/authentik/tasks/broker.py @@ -319,20 +319,29 @@ class _PostgresConsumer(Consumer): @raise_connection_error def requeue(self, messages: Iterable[Message]): + for message in messages: + self.unlock_queue.put_nowait(message) self.query_set.filter( message_id__in=[message.message_id for message in messages], ).update( state=TaskState.QUEUED, ) - # We don't care about locks, requeue occurs on worker stop - # TODO: this is not true, we need to handle them + for message in messages: + self.in_processing.remove(message.message_id) + self._purge_locks() def _fetch_pending_notifies(self) -> list[Notify]: self.logger.debug(f"Polling for lost messages in {self.queue_name}") - notifies = self.query_set.filter( - state__in=(TaskState.QUEUED, TaskState.CONSUMED), - queue_name=self.queue_name, - ).values_list("message_id", flat=True) + notifies = ( + self.query_set.filter( + state__in=(TaskState.QUEUED, TaskState.CONSUMED), + queue_name=self.queue_name, + ) + .exclude( + message_id__in=self.in_processing, + ) + .values_list("message_id", flat=True) + ) channel = channel_name(self.queue_name, ChannelIdentifier.ENQUEUE) return [Notify(pid=0, channel=channel, payload=item) for item in notifies] @@ -383,7 +392,7 @@ class _PostgresConsumer(Consumer): processing = len(self.in_processing) if processing >= self.prefetch: - # Wait and don't consume the message, other worker will be fast + # Wait and don't consume the message, other worker will be faster self.misses, backoff_ms = compute_backoff(self.misses, max_backoff=1000) self.logger.debug( f"Too many messages in processing: {processing}. Sleeping {backoff_ms} ms" diff --git a/authentik/tasks/management/commands/worker.py b/authentik/tasks/management/commands/worker.py index e45faa56e1..85d45d5c6b 100644 --- a/authentik/tasks/management/commands/worker.py +++ b/authentik/tasks/management/commands/worker.py @@ -1,6 +1,7 @@ import os import sys +from django.conf import settings from django.core.management.base import BaseCommand from django.utils.module_loading import module_has_submodule @@ -48,7 +49,7 @@ class Command(BaseCommand): ): executable_name = "dramatiq-gevent" if use_gevent else "dramatiq" executable_path = self._resolve_executable(executable_name) - watch_args = ["--watch", "."] if use_watcher else [] + watch_args = ["--watch", "."] if use_watcher or settings.DEBUG else [] if watch_args and use_polling_watcher: watch_args.append("--watch-use-polling") diff --git a/authentik/tasks/schedules/api.py b/authentik/tasks/schedules/api.py index a986a64dba..69cf2c891d 100644 --- a/authentik/tasks/schedules/api.py +++ b/authentik/tasks/schedules/api.py @@ -35,8 +35,8 @@ class ScheduleSerializer(ModelSerializer): try: actor: Actor = get_broker().get_actor(instance.actor_name) except ActorNotFound: - return None - return actor.fn.__doc__ + return "FIXME this shouldn't happen" + return actor.fn.__doc__.strip() class ScheduleViewSet( diff --git a/authentik/tasks/schedules/apps.py b/authentik/tasks/schedules/apps.py index 21aba19e13..9cb8775556 100644 --- a/authentik/tasks/schedules/apps.py +++ b/authentik/tasks/schedules/apps.py @@ -1,4 +1,5 @@ from authentik.blueprints.apps import ManagedAppConfig +from authentik.lib.utils.reflection import get_apps from authentik.tasks.schedules.lib import ScheduleSpec @@ -21,3 +22,26 @@ class AuthentikTasksSchedulesConfig(ManagedAppConfig): spec.rel_obj = obj schedules.append(spec) return schedules + + def _reconcile_schedules(self, specs: list[ScheduleSpec]): + from django.db import transaction + + from authentik.tasks.schedules.models import Schedule + + with transaction.atomic(): + pks_to_keep = [] + for spec in specs: + schedule = spec.update_or_create() + pks_to_keep.append(schedule.pk) + Schedule.objects.exclude(pk__in=pks_to_keep).delete() + + @ManagedAppConfig.reconcile_tenant + def reconcile_tenant_schedules(self): + from authentik.tenants.utils import get_current_tenant, get_public_schema_name + + schedule_specs = [] + for app in get_apps(): + schedule_specs.extend(app.tenant_schedule_specs) + if get_current_tenant().schema_name == get_public_schema_name(): + schedule_specs.extend(app.global_schedule_specs) + self._reconcile_schedules(schedule_specs) diff --git a/authentik/tasks/setup.py b/authentik/tasks/setup.py index 5869bf8107..6b480ea393 100644 --- a/authentik/tasks/setup.py +++ b/authentik/tasks/setup.py @@ -1,14 +1,10 @@ import os -import sys import warnings from cryptography.hazmat.backends.openssl.backend import backend from defusedxml import defuse_stdlib -from django.utils.autoreload import DJANGO_AUTORELOAD_ENV from authentik.lib.config import CONFIG -from lifecycle.migrate import run_migrations -from lifecycle.wait_for_db import wait_for_db warnings.filterwarnings("ignore", "SelectableGroups dict interface") warnings.filterwarnings( @@ -25,21 +21,15 @@ defuse_stdlib() if CONFIG.get_bool("compliance.fips.enabled", False): backend._enable_fips() - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings") -wait_for_db() -print(sys.argv) -if ( - len(sys.argv) > 1 - # Explicitly only run migrate for server and worker - # `bootstrap_tasks` is a special case as that command might be triggered by the `ak` - # script to pre-run certain tasks for an automated install - and sys.argv[1] in ["dev_server", "worker", "bootstrap_tasks"] - # and don't run if this is the child process of a dev_server - and os.environ.get(DJANGO_AUTORELOAD_ENV, None) is None -): - run_migrations() import django # noqa: E402 django.setup() + +from authentik.root.signals import post_startup, pre_startup, startup # noqa: E402 + +_startup_sender = type("WorkerStartup", (object,), {}) +pre_startup.send(sender=_startup_sender) +startup.send(sender=_startup_sender) +post_startup.send(sender=_startup_sender) diff --git a/internal/web/web.go b/internal/web/web.go index d5b2ae88ef..615050c19a 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -108,6 +108,7 @@ func NewWebServer() *WebServer { func (ws *WebServer) Start() { go ws.runMetricsServer() go ws.attemptStartBackend() + go ws.attemptStartWorker() go ws.listenPlain() go ws.listenTLS() } @@ -137,6 +138,12 @@ func (ws *WebServer) attemptStartBackend() { } } +func (ws *WebServer) attemptStartWorker() { + if ws.worker == nil { + return + } +} + func (ws *WebServer) Core() *gounicorn.GoUnicorn { return ws.g } diff --git a/internal/worker/worker.go b/internal/worker/worker.go index 6d3d2406e0..6bc92b0c64 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -30,7 +30,7 @@ type Worker struct { } func New(healthcheck func() bool) *Worker { - logger := log.WithField("logger", "authentik.router.unicorn") + logger := log.WithField("logger", "authentik.router.worker") w := &Worker{ Healthcheck: healthcheck, log: logger, diff --git a/manage.py b/manage.py index 09d065e919..9e32f80623 100755 --- a/manage.py +++ b/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django manage.py""" + import os import sys import warnings