mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-18 03:19:51 +03:00
85759d5fd2
* web/admin: consistent OS display Signed-off-by: Jens Langhammer <jens@goauthentik.io> * include license status with agent config Signed-off-by: Jens Langhammer <jens@goauthentik.io> * slightly rework Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
565 lines
19 KiB
Python
565 lines
19 KiB
Python
"""root settings for authentik"""
|
|
|
|
import importlib
|
|
from collections import OrderedDict
|
|
from hashlib import sha512
|
|
from pathlib import Path
|
|
|
|
import orjson
|
|
from django.utils import http as utils_http
|
|
from sentry_sdk import set_tag
|
|
from xmlsec import enable_debug_trace
|
|
|
|
from authentik import authentik_version
|
|
from authentik.lib.config import CONFIG, django_db_config
|
|
from authentik.lib.logging import get_logger_config, structlog_configure
|
|
from authentik.lib.sentry import sentry_init
|
|
from authentik.lib.utils.reflection import get_env
|
|
from authentik.lib.utils.time import timedelta_from_string
|
|
from authentik.stages.password import BACKEND_APP_PASSWORD, BACKEND_INBUILT, BACKEND_LDAP
|
|
|
|
BASE_DIR = Path(__file__).absolute().parent.parent.parent
|
|
|
|
DEBUG = CONFIG.get_bool("debug")
|
|
SECRET_KEY = CONFIG.get("secret_key")
|
|
|
|
INTERNAL_IPS = ["127.0.0.1"]
|
|
ALLOWED_HOSTS = ["*"]
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
SECURE_CROSS_ORIGIN_OPENER_POLICY = None
|
|
LOGIN_URL = "authentik_flows:default-authentication"
|
|
|
|
# Custom user model
|
|
AUTH_USER_MODEL = "authentik_core.User"
|
|
|
|
CSRF_COOKIE_PATH = LANGUAGE_COOKIE_PATH = SESSION_COOKIE_PATH = CONFIG.get("web.path", "/")
|
|
|
|
CSRF_COOKIE_NAME = "authentik_csrf"
|
|
CSRF_HEADER_NAME = "HTTP_X_AUTHENTIK_CSRF"
|
|
LANGUAGE_COOKIE_NAME = "authentik_language"
|
|
SESSION_COOKIE_NAME = "authentik_session"
|
|
SESSION_COOKIE_DOMAIN = CONFIG.get("cookie_domain", None)
|
|
APPEND_SLASH = False
|
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
BACKEND_INBUILT,
|
|
BACKEND_APP_PASSWORD,
|
|
BACKEND_LDAP,
|
|
"guardian.backends.ObjectPermissionBackend",
|
|
]
|
|
|
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|
|
|
# Application definition
|
|
SHARED_APPS = [
|
|
"authentik.commands",
|
|
"django_tenants",
|
|
"authentik.tenants",
|
|
"django.contrib.messages",
|
|
"django.contrib.staticfiles",
|
|
"django.contrib.humanize",
|
|
"django.contrib.postgres",
|
|
"psqlextra",
|
|
"rest_framework",
|
|
"django_filters",
|
|
"drf_spectacular",
|
|
"django_prometheus",
|
|
"django_countries",
|
|
"pgactivity",
|
|
"pglock",
|
|
"channels",
|
|
"django_channels_postgres",
|
|
"django_dramatiq_postgres",
|
|
"authentik.tasks",
|
|
]
|
|
TENANT_APPS = [
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.sessions",
|
|
"pgtrigger",
|
|
"django_postgres_cache",
|
|
"authentik.admin",
|
|
"authentik.api",
|
|
"authentik.core",
|
|
"authentik.crypto",
|
|
"authentik.endpoints",
|
|
"authentik.endpoints.connectors.agent",
|
|
"authentik.enterprise",
|
|
"authentik.events",
|
|
"authentik.admin.files",
|
|
"authentik.flows",
|
|
"authentik.outposts",
|
|
"authentik.policies.dummy",
|
|
"authentik.policies.event_matcher",
|
|
"authentik.policies.expiry",
|
|
"authentik.policies.expression",
|
|
"authentik.policies.geoip",
|
|
"authentik.policies.password",
|
|
"authentik.policies.reputation",
|
|
"authentik.policies",
|
|
"authentik.providers.ldap",
|
|
"authentik.providers.oauth2",
|
|
"authentik.providers.proxy",
|
|
"authentik.providers.rac",
|
|
"authentik.providers.radius",
|
|
"authentik.providers.saml",
|
|
"authentik.providers.scim",
|
|
"authentik.rbac",
|
|
"authentik.recovery",
|
|
"authentik.sources.kerberos",
|
|
"authentik.sources.ldap",
|
|
"authentik.sources.oauth",
|
|
"authentik.sources.plex",
|
|
"authentik.sources.saml",
|
|
"authentik.sources.scim",
|
|
"authentik.sources.telegram",
|
|
"authentik.stages.authenticator",
|
|
"authentik.stages.authenticator_duo",
|
|
"authentik.stages.authenticator_email",
|
|
"authentik.stages.authenticator_sms",
|
|
"authentik.stages.authenticator_static",
|
|
"authentik.stages.authenticator_totp",
|
|
"authentik.stages.authenticator_validate",
|
|
"authentik.stages.authenticator_webauthn",
|
|
"authentik.stages.captcha",
|
|
"authentik.stages.consent",
|
|
"authentik.stages.deny",
|
|
"authentik.stages.dummy",
|
|
"authentik.stages.email",
|
|
"authentik.stages.identification",
|
|
"authentik.stages.invitation",
|
|
"authentik.stages.password",
|
|
"authentik.stages.prompt",
|
|
"authentik.stages.redirect",
|
|
"authentik.stages.user_delete",
|
|
"authentik.stages.user_login",
|
|
"authentik.stages.user_logout",
|
|
"authentik.stages.user_write",
|
|
"authentik.tasks.schedules",
|
|
"authentik.brands",
|
|
"authentik.blueprints",
|
|
"guardian",
|
|
]
|
|
|
|
TENANT_MODEL = "authentik_tenants.Tenant"
|
|
TENANT_DOMAIN_MODEL = "authentik_tenants.Domain"
|
|
|
|
TENANT_CREATION_FAKES_MIGRATIONS = True
|
|
TENANT_BASE_SCHEMA = "template"
|
|
PUBLIC_SCHEMA_NAME = CONFIG.get("postgresql.default_schema")
|
|
|
|
GUARDIAN_ROLE_MODEL = "authentik_rbac.Role"
|
|
|
|
SPECTACULAR_SETTINGS = {
|
|
"TITLE": "authentik",
|
|
"DESCRIPTION": "Making authentication simple.",
|
|
"VERSION": authentik_version(),
|
|
"COMPONENT_SPLIT_REQUEST": True,
|
|
"SCHEMA_PATH_PREFIX": "/api/v([0-9]+(beta)?)",
|
|
"SCHEMA_PATH_PREFIX_TRIM": True,
|
|
"SERVERS": [
|
|
{
|
|
"url": "/api/v3",
|
|
},
|
|
],
|
|
"CONTACT": {
|
|
"email": "hello@goauthentik.io",
|
|
},
|
|
"AUTHENTICATION_WHITELIST": ["authentik.api.authentication.TokenAuthentication"],
|
|
"LICENSE": {
|
|
"name": "MIT",
|
|
"url": "https://github.com/goauthentik/authentik/blob/main/LICENSE",
|
|
},
|
|
"ENUM_NAME_OVERRIDES": {
|
|
"CountryCodeEnum": "django_countries.countries",
|
|
"DeviceClassesEnum": "authentik.stages.authenticator_validate.models.DeviceClasses",
|
|
"EventActions": "authentik.events.models.EventAction",
|
|
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
|
|
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
|
|
"LDAPAPIAccessMode": "authentik.providers.ldap.models.APIAccessMode",
|
|
"OutgoingSyncDeleteAction": "authentik.lib.sync.outgoing.models.OutgoingSyncDeleteAction",
|
|
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
|
|
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
|
|
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
|
|
"TaskAggregatedStatusEnum": "authentik.tasks.models.TaskStatus",
|
|
"SAMLNameIDPolicyEnum": "authentik.sources.saml.models.SAMLNameIDPolicy",
|
|
"SAMLBindingsEnum": "authentik.providers.saml.models.SAMLBindings",
|
|
"UserTypeEnum": "authentik.core.models.UserTypes",
|
|
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
|
|
"SCIMAuthenticationModeEnum": "authentik.providers.scim.models.SCIMAuthenticationMode",
|
|
"PKCEMethodEnum": "authentik.sources.oauth.models.PKCEMethod",
|
|
"DeviceFactsOSFamily": "authentik.endpoints.facts.OSFamily",
|
|
"StageModeEnum": "authentik.endpoints.models.StageMode",
|
|
"LicenseSummaryStatusEnum": "authentik.enterprise.models.LicenseUsageStatus",
|
|
},
|
|
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
|
|
"ENUM_GENERATE_CHOICE_DESCRIPTION": False,
|
|
"PREPROCESSING_HOOKS": [
|
|
"authentik.api.schema.preprocess_schema_exclude_non_api",
|
|
],
|
|
"POSTPROCESSING_HOOKS": [
|
|
"authentik.api.schema.postprocess_schema_register",
|
|
"authentik.api.schema.postprocess_schema_responses",
|
|
"authentik.api.schema.postprocess_schema_query_params",
|
|
"authentik.api.schema.postprocess_schema_remove_unused",
|
|
"drf_spectacular.hooks.postprocess_schema_enums",
|
|
],
|
|
}
|
|
|
|
REST_FRAMEWORK = {
|
|
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
|
|
"DEFAULT_FILTER_BACKENDS": [
|
|
"authentik.rbac.filters.ObjectFilter",
|
|
"django_filters.rest_framework.DjangoFilterBackend",
|
|
"rest_framework.filters.OrderingFilter",
|
|
"rest_framework.filters.SearchFilter",
|
|
],
|
|
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
|
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
|
"authentik.api.authentication.TokenAuthentication",
|
|
"rest_framework.authentication.SessionAuthentication",
|
|
),
|
|
"DEFAULT_RENDERER_CLASSES": [
|
|
"drf_orjson_renderer.renderers.ORJSONRenderer",
|
|
],
|
|
"ORJSON_RENDERER_OPTIONS": [
|
|
orjson.OPT_NON_STR_KEYS,
|
|
orjson.OPT_UTC_Z,
|
|
],
|
|
"DEFAULT_PARSER_CLASSES": [
|
|
"drf_orjson_renderer.parsers.ORJSONParser",
|
|
],
|
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
|
"TEST_REQUEST_DEFAULT_FORMAT": "json",
|
|
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.AnonRateThrottle"],
|
|
"DEFAULT_THROTTLE_RATES": {
|
|
"anon": CONFIG.get("throttle.default"),
|
|
},
|
|
}
|
|
|
|
|
|
CACHES = {
|
|
"default": {
|
|
"BACKEND": "django_postgres_cache.backend.DatabaseCache",
|
|
"KEY_FUNCTION": "django_tenants.cache.make_key",
|
|
"REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key",
|
|
}
|
|
}
|
|
SESSION_ENGINE = "authentik.core.sessions"
|
|
# Configured via custom SessionMiddleware
|
|
# SESSION_COOKIE_SAMESITE = "None"
|
|
# SESSION_COOKIE_SECURE = True
|
|
SESSION_COOKIE_AGE = timedelta_from_string(
|
|
CONFIG.get("sessions.unauthenticated_age", "days=1")
|
|
).total_seconds()
|
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
|
|
|
MESSAGE_STORAGE = "authentik.root.ws.storage.ChannelsStorage"
|
|
|
|
MIDDLEWARE_FIRST = [
|
|
"django_prometheus.middleware.PrometheusBeforeMiddleware",
|
|
]
|
|
MIDDLEWARE = [
|
|
"authentik.tenants.middleware.DefaultTenantMiddleware",
|
|
"authentik.root.middleware.LoggingMiddleware",
|
|
"authentik.root.middleware.ClientIPMiddleware",
|
|
"authentik.stages.user_login.middleware.BoundSessionMiddleware",
|
|
"django.middleware.locale.LocaleMiddleware",
|
|
"authentik.core.middleware.AuthenticationMiddleware",
|
|
"authentik.core.middleware.RequestIDMiddleware",
|
|
"authentik.brands.middleware.BrandMiddleware",
|
|
"authentik.events.middleware.AuditMiddleware",
|
|
"django.middleware.security.SecurityMiddleware",
|
|
"django.middleware.common.CommonMiddleware",
|
|
"authentik.root.middleware.CsrfViewMiddleware",
|
|
"django.contrib.messages.middleware.MessageMiddleware",
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
"authentik.core.middleware.ImpersonateMiddleware",
|
|
"authentik.rbac.middleware.InitialPermissionsMiddleware",
|
|
]
|
|
MIDDLEWARE_LAST = [
|
|
"django_prometheus.middleware.PrometheusAfterMiddleware",
|
|
]
|
|
|
|
ROOT_URLCONF = "authentik.root.urls"
|
|
|
|
TEMPLATES = [
|
|
{
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
"DIRS": [CONFIG.get("email.template_dir")],
|
|
"APP_DIRS": True,
|
|
"OPTIONS": {
|
|
"context_processors": [
|
|
"django.template.context_processors.debug",
|
|
"django.template.context_processors.request",
|
|
"django.contrib.auth.context_processors.auth",
|
|
"django.contrib.messages.context_processors.messages",
|
|
"authentik.brands.utils.context_processor",
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
ASGI_APPLICATION = "authentik.root.asgi.application"
|
|
|
|
|
|
# Database
|
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
|
|
|
# Custom overrides for database backends
|
|
# The tree looks like this:
|
|
# psqlextra backend
|
|
# -> authentik custom backend
|
|
# -> django_tenants backend
|
|
# -> django_prometheus backend
|
|
# -> django built-in backend
|
|
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
|
|
POSTGRES_EXTRA_DB_BACKEND_BASE = "authentik.root.db"
|
|
DATABASES = django_db_config()
|
|
|
|
DATABASE_ROUTERS = (
|
|
"authentik.tenants.db.FailoverRouter",
|
|
"django_tenants.routers.TenantSyncRouter",
|
|
)
|
|
|
|
# We don't use HStore
|
|
POSTGRES_EXTRA_AUTO_EXTENSION_SET_UP = False
|
|
|
|
CHANNEL_LAYERS = {
|
|
"default": {
|
|
"BACKEND": "django_channels_postgres.layer.PostgresChannelLayer",
|
|
},
|
|
}
|
|
|
|
# Email
|
|
# These values should never actually be used, emails are only sent from email stages, which
|
|
# loads the config directly from CONFIG
|
|
# See authentik/stages/email/models.py, line 105
|
|
EMAIL_HOST = CONFIG.get("email.host")
|
|
EMAIL_PORT = CONFIG.get_int("email.port")
|
|
EMAIL_HOST_USER = CONFIG.get("email.username")
|
|
EMAIL_HOST_PASSWORD = CONFIG.get("email.password")
|
|
EMAIL_USE_TLS = CONFIG.get_bool("email.use_tls", False)
|
|
EMAIL_USE_SSL = CONFIG.get_bool("email.use_ssl", False)
|
|
EMAIL_TIMEOUT = CONFIG.get_int("email.timeout")
|
|
DEFAULT_FROM_EMAIL = CONFIG.get("email.from")
|
|
SERVER_EMAIL = DEFAULT_FROM_EMAIL
|
|
EMAIL_SUBJECT_PREFIX = "[authentik] "
|
|
|
|
# Password validation
|
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
|
|
|
|
AUTH_PASSWORD_VALIDATORS = [
|
|
{
|
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
|
},
|
|
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
|
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
|
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
|
]
|
|
|
|
|
|
# Internationalization
|
|
# https://docs.djangoproject.com/en/2.1/topics/i18n/
|
|
|
|
LANGUAGE_CODE = "en-us"
|
|
|
|
TIME_ZONE = "UTC"
|
|
|
|
USE_I18N = True
|
|
|
|
USE_TZ = True
|
|
|
|
LOCALE_PATHS = ["./locale"]
|
|
|
|
|
|
# Tests
|
|
|
|
TEST = False
|
|
TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner"
|
|
|
|
|
|
# Dramatiq
|
|
|
|
DRAMATIQ = {
|
|
"broker_class": "authentik.tasks.broker.Broker",
|
|
"channel_prefix": "authentik",
|
|
"task_model": "authentik.tasks.models.Task",
|
|
"task_purge_interval": timedelta_from_string(
|
|
CONFIG.get("worker.task_purge_interval")
|
|
).total_seconds(),
|
|
"task_expiration": timedelta_from_string(CONFIG.get("worker.task_expiration")).total_seconds(),
|
|
"autodiscovery": {
|
|
"enabled": True,
|
|
"setup_module": "authentik.tasks.setup",
|
|
"apps_prefix": "authentik",
|
|
},
|
|
"worker": {
|
|
"processes": CONFIG.get_int("worker.processes", 2),
|
|
"threads": CONFIG.get_int("worker.threads", 1),
|
|
"consumer_listen_timeout": timedelta_from_string(
|
|
CONFIG.get("worker.consumer_listen_timeout")
|
|
).total_seconds(),
|
|
"watch_folder": BASE_DIR / "authentik",
|
|
},
|
|
"scheduler_class": "authentik.tasks.schedules.scheduler.Scheduler",
|
|
"schedule_model": "authentik.tasks.schedules.models.Schedule",
|
|
"scheduler_interval": timedelta_from_string(
|
|
CONFIG.get("worker.scheduler_interval")
|
|
).total_seconds(),
|
|
"middlewares": (
|
|
("django_dramatiq_postgres.middleware.FullyQualifiedActorName", {}),
|
|
("django_dramatiq_postgres.middleware.DbConnectionMiddleware", {}),
|
|
("django_dramatiq_postgres.middleware.TaskStateBeforeMiddleware", {}),
|
|
("dramatiq.middleware.age_limit.AgeLimit", {}),
|
|
(
|
|
"dramatiq.middleware.time_limit.TimeLimit",
|
|
{
|
|
"time_limit": timedelta_from_string(
|
|
CONFIG.get("worker.task_default_time_limit")
|
|
).total_seconds()
|
|
* 1000
|
|
},
|
|
),
|
|
("dramatiq.middleware.shutdown.ShutdownNotifications", {}),
|
|
("dramatiq.middleware.callbacks.Callbacks", {}),
|
|
("dramatiq.middleware.pipelines.Pipelines", {}),
|
|
(
|
|
"dramatiq.middleware.retries.Retries",
|
|
{
|
|
"max_retries": CONFIG.get_int("worker.task_max_retries") if not TEST else 0,
|
|
"max_backoff": 60 * 60 * 1000, # 1 hour
|
|
},
|
|
),
|
|
("dramatiq.results.middleware.Results", {"store_results": True}),
|
|
("authentik.tasks.middleware.StartupSignalsMiddleware", {}),
|
|
("authentik.tasks.middleware.CurrentTask", {}),
|
|
("authentik.tasks.middleware.TenantMiddleware", {}),
|
|
("authentik.tasks.middleware.ModelDataMiddleware", {}),
|
|
("authentik.tasks.middleware.TaskLogMiddleware", {}),
|
|
("authentik.tasks.middleware.LoggingMiddleware", {}),
|
|
("authentik.tasks.middleware.DescriptionMiddleware", {}),
|
|
("authentik.tasks.middleware.WorkerHealthcheckMiddleware", {}),
|
|
("authentik.tasks.middleware.WorkerStatusMiddleware", {}),
|
|
(
|
|
"authentik.tasks.middleware.MetricsMiddleware",
|
|
{
|
|
"prefix": "authentik",
|
|
},
|
|
),
|
|
("django_dramatiq_postgres.middleware.TaskStateAfterMiddleware", {}),
|
|
),
|
|
"test": TEST,
|
|
}
|
|
|
|
|
|
# Sentry integration
|
|
|
|
env = get_env()
|
|
_ERROR_REPORTING = CONFIG.get_bool("error_reporting.enabled", False)
|
|
if _ERROR_REPORTING:
|
|
sentry_env = CONFIG.get("error_reporting.environment", "customer")
|
|
sentry_init(spotlight=DEBUG)
|
|
set_tag("authentik.uuid", sha512(str(SECRET_KEY).encode("ascii")).hexdigest()[:16])
|
|
|
|
|
|
# Static files (CSS, JavaScript, Images)
|
|
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
|
|
|
STATICFILES_DIRS = [BASE_DIR / Path("web")]
|
|
STATIC_URL = CONFIG.get("web.path", "/") + "static/"
|
|
|
|
STORAGES = {
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
|
},
|
|
}
|
|
|
|
# Django 5.2.8 and CVE-2025-64458 added a strong enforcement of 2048 characters
|
|
# as the maximum for a URL to redirect to, mostly for running on Windows.
|
|
# However, our URLs can easily exceed that with OAuth/SAML Query parameters or hash values.
|
|
# 8192 should cover most cases.
|
|
utils_http.MAX_URL_LENGTH = utils_http.MAX_URL_LENGTH * 4
|
|
|
|
structlog_configure()
|
|
LOGGING = get_logger_config()
|
|
|
|
|
|
_DISALLOWED_ITEMS = [
|
|
"SHARED_APPS",
|
|
"TENANT_APPS",
|
|
"INSTALLED_APPS",
|
|
"MIDDLEWARE_FIRST",
|
|
"MIDDLEWARE",
|
|
"MIDDLEWARE_LAST",
|
|
"AUTHENTICATION_BACKENDS",
|
|
"SPECTACULAR_SETTINGS",
|
|
"REST_FRAMEWORK",
|
|
]
|
|
|
|
SILENCED_SYSTEM_CHECKS = [
|
|
# We use our own subclass of django.middleware.csrf.CsrfViewMiddleware
|
|
"security.W003",
|
|
# We don't set SESSION_COOKIE_SECURE since we use a custom SessionMiddleware subclass
|
|
"security.W010",
|
|
# HSTS: This is configured in reverse proxies/the go proxy, not in django
|
|
"security.W004",
|
|
# https redirect: This is configured in reverse proxies/the go proxy, not in django
|
|
"security.W008",
|
|
]
|
|
|
|
|
|
def subtract_list(a: list, b: list) -> list:
|
|
return [item for item in a if item not in b]
|
|
|
|
|
|
def _filter_and_update(apps: list[str]) -> None:
|
|
for _app in set(apps):
|
|
if not _app.startswith("authentik"):
|
|
continue
|
|
_update_settings(f"{_app}.settings")
|
|
|
|
|
|
def _update_settings(app_path: str) -> None:
|
|
try:
|
|
settings_module = importlib.import_module(app_path)
|
|
CONFIG.log("debug", "Loaded app settings", path=app_path)
|
|
|
|
new_shared_apps = subtract_list(getattr(settings_module, "SHARED_APPS", []), SHARED_APPS)
|
|
new_tenant_apps = subtract_list(getattr(settings_module, "TENANT_APPS", []), TENANT_APPS)
|
|
SHARED_APPS.extend(new_shared_apps)
|
|
TENANT_APPS.extend(new_tenant_apps)
|
|
_filter_and_update(new_shared_apps + new_tenant_apps)
|
|
|
|
MIDDLEWARE_FIRST.extend(getattr(settings_module, "MIDDLEWARE_FIRST", []))
|
|
MIDDLEWARE.extend(getattr(settings_module, "MIDDLEWARE", []))
|
|
|
|
AUTHENTICATION_BACKENDS.extend(getattr(settings_module, "AUTHENTICATION_BACKENDS", []))
|
|
SPECTACULAR_SETTINGS.update(getattr(settings_module, "SPECTACULAR_SETTINGS", {}))
|
|
REST_FRAMEWORK.update(getattr(settings_module, "REST_FRAMEWORK", {}))
|
|
|
|
for _attr in dir(settings_module):
|
|
if not _attr.startswith("__") and _attr not in _DISALLOWED_ITEMS:
|
|
globals()[_attr] = getattr(settings_module, _attr)
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
if DEBUG:
|
|
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append(
|
|
"rest_framework.renderers.BrowsableAPIRenderer"
|
|
)
|
|
SHARED_APPS.insert(SHARED_APPS.index("django.contrib.staticfiles"), "daphne")
|
|
enable_debug_trace(True)
|
|
|
|
|
|
CONFIG.log("info", "Booting authentik", version=authentik_version())
|
|
|
|
# Load subapps's settings
|
|
_filter_and_update(SHARED_APPS + TENANT_APPS)
|
|
_update_settings("data.user_settings")
|
|
|
|
MIDDLEWARE = list(OrderedDict.fromkeys(MIDDLEWARE_FIRST + MIDDLEWARE + MIDDLEWARE_LAST))
|
|
SHARED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
|
|
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
|