![gcp-cherry-pick-bot[bot]](/assets/img/avatar_default.png)
core: include version in built JS files (#9558) * web: fix esbuild issue with style sheets Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious pain. This fix better identifies the value types (instances) being passed from various sources in the repo to the three *different* kinds of style processors we're using (the native one, the polyfill one, and whatever the heck Storybook does internally). Falling back to using older CSS instantiating techniques one era at a time seems to do the trick. It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content (FLoUC), it's the logic with which we're left. In standard mode, the following warning appears on the console when running a Flow: ``` Autofocus processing was blocked because a document already has a focused element. ``` In compatibility mode, the following **error** appears on the console when running a Flow: ``` crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'. at initDomMutationObservers (crawler-inject.js:1106:18) at crawler-inject.js:1114:24 at Array.forEach (<anonymous>) at initDomMutationObservers (crawler-inject.js:1114:10) at crawler-inject.js:1549:1 initDomMutationObservers @ crawler-inject.js:1106 (anonymous) @ crawler-inject.js:1114 initDomMutationObservers @ crawler-inject.js:1114 (anonymous) @ crawler-inject.js:1549 ``` Despite this error, nothing seems to be broken and flows work as anticipated. * core: include version in built JS files * add fallback * include build hash * format * fix stuff why does this even work locally * idk man node * just not use import assertions * web: add no-console, use proper dirname path * web: retarget to use the base package.json file. * web: encode path to root package.json using git This is the most authoritative way of finding the root of the git project. * use full version to match frontend * add fallback for missing .git folder --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Jens L <jens@goauthentik.io> Co-authored-by: Ken Sternberg <ken@goauthentik.io>
544 lines
19 KiB
Python
544 lines
19 KiB
Python
"""root settings for authentik"""
|
|
|
|
import importlib
|
|
from collections import OrderedDict
|
|
from hashlib import sha512
|
|
from pathlib import Path
|
|
|
|
from celery.schedules import crontab
|
|
from django.conf import ImproperlyConfigured
|
|
from sentry_sdk import set_tag
|
|
|
|
from authentik import __version__
|
|
from authentik.lib.config import CONFIG, redis_url
|
|
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.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_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 = [
|
|
"django.contrib.auth.backends.ModelBackend",
|
|
BACKEND_INBUILT,
|
|
BACKEND_APP_PASSWORD,
|
|
BACKEND_LDAP,
|
|
"guardian.backends.ObjectPermissionBackend",
|
|
]
|
|
|
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|
|
|
# Application definition
|
|
SHARED_APPS = [
|
|
"django_tenants",
|
|
"authentik.tenants",
|
|
"daphne",
|
|
"django.contrib.messages",
|
|
"django.contrib.staticfiles",
|
|
"django.contrib.humanize",
|
|
"rest_framework",
|
|
"django_filters",
|
|
"drf_spectacular",
|
|
"django_prometheus",
|
|
"pgactivity",
|
|
"pglock",
|
|
"channels",
|
|
]
|
|
TENANT_APPS = [
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.sessions",
|
|
"authentik.admin",
|
|
"authentik.api",
|
|
"authentik.crypto",
|
|
"authentik.flows",
|
|
"authentik.outposts",
|
|
"authentik.policies.dummy",
|
|
"authentik.policies.event_matcher",
|
|
"authentik.policies.expiry",
|
|
"authentik.policies.expression",
|
|
"authentik.policies.password",
|
|
"authentik.policies.reputation",
|
|
"authentik.policies",
|
|
"authentik.providers.ldap",
|
|
"authentik.providers.oauth2",
|
|
"authentik.providers.proxy",
|
|
"authentik.providers.radius",
|
|
"authentik.providers.saml",
|
|
"authentik.providers.scim",
|
|
"authentik.rbac",
|
|
"authentik.recovery",
|
|
"authentik.sources.ldap",
|
|
"authentik.sources.oauth",
|
|
"authentik.sources.plex",
|
|
"authentik.sources.saml",
|
|
"authentik.sources.scim",
|
|
"authentik.stages.authenticator",
|
|
"authentik.stages.authenticator_duo",
|
|
"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.user_delete",
|
|
"authentik.stages.user_login",
|
|
"authentik.stages.user_logout",
|
|
"authentik.stages.user_write",
|
|
"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"
|
|
|
|
GUARDIAN_MONKEY_PATCH = False
|
|
|
|
SPECTACULAR_SETTINGS = {
|
|
"TITLE": "authentik",
|
|
"DESCRIPTION": "Making authentication simple.",
|
|
"VERSION": __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": {
|
|
"EventActions": "authentik.events.models.EventAction",
|
|
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
|
|
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
|
|
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
|
|
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
|
|
"ProxyMode": "authentik.providers.proxy.models.ProxyMode",
|
|
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
|
|
"LDAPAPIAccessMode": "authentik.providers.ldap.models.APIAccessMode",
|
|
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
|
|
"UserTypeEnum": "authentik.core.models.UserTypes",
|
|
"OutgoingSyncDeleteAction": "authentik.lib.sync.outgoing.models.OutgoingSyncDeleteAction",
|
|
},
|
|
"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_responses",
|
|
"drf_spectacular.hooks.postprocess_schema_enums",
|
|
],
|
|
}
|
|
|
|
REST_FRAMEWORK = {
|
|
"DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination",
|
|
"PAGE_SIZE": 100,
|
|
"DEFAULT_FILTER_BACKENDS": [
|
|
"authentik.rbac.filters.ObjectFilter",
|
|
"django_filters.rest_framework.DjangoFilterBackend",
|
|
"rest_framework.filters.OrderingFilter",
|
|
"rest_framework.filters.SearchFilter",
|
|
],
|
|
"DEFAULT_PARSER_CLASSES": [
|
|
"rest_framework.parsers.JSONParser",
|
|
],
|
|
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
|
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
|
"authentik.api.authentication.TokenAuthentication",
|
|
"rest_framework.authentication.SessionAuthentication",
|
|
),
|
|
"DEFAULT_RENDERER_CLASSES": [
|
|
"rest_framework.renderers.JSONRenderer",
|
|
],
|
|
"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_redis.cache.RedisCache",
|
|
"LOCATION": CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db")),
|
|
"TIMEOUT": CONFIG.get_int("cache.timeout", 300),
|
|
"OPTIONS": {
|
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
},
|
|
"KEY_PREFIX": "authentik_cache",
|
|
"KEY_FUNCTION": "django_tenants.cache.make_key",
|
|
"REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key",
|
|
}
|
|
}
|
|
DJANGO_REDIS_SCAN_ITERSIZE = 1000
|
|
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
|
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
|
|
match CONFIG.get("session_storage", "cache"):
|
|
case "cache":
|
|
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
|
case "db":
|
|
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
|
case _:
|
|
raise ImproperlyConfigured(
|
|
"Invalid session_storage setting, allowed values are db and cache"
|
|
)
|
|
SESSION_SERIALIZER = "authentik.root.sessions.pickle.PickleSerializer"
|
|
SESSION_CACHE_ALIAS = "default"
|
|
# Configured via custom SessionMiddleware
|
|
# SESSION_COOKIE_SAMESITE = "None"
|
|
# SESSION_COOKIE_SECURE = True
|
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
|
|
|
MESSAGE_STORAGE = "authentik.root.messages.storage.ChannelsStorage"
|
|
|
|
MIDDLEWARE = [
|
|
"django_tenants.middleware.default.DefaultTenantMiddleware",
|
|
"authentik.root.middleware.LoggingMiddleware",
|
|
"django_prometheus.middleware.PrometheusBeforeMiddleware",
|
|
"authentik.root.middleware.ClientIPMiddleware",
|
|
"authentik.stages.user_login.middleware.BoundSessionMiddleware",
|
|
"django.contrib.auth.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",
|
|
"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"
|
|
|
|
CHANNEL_LAYERS = {
|
|
"default": {
|
|
"BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer",
|
|
"CONFIG": {
|
|
"hosts": [CONFIG.get("channel.url") or redis_url(CONFIG.get("redis.db"))],
|
|
"prefix": "authentik_channels_",
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
# Database
|
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
|
|
|
ORIGINAL_BACKEND = "django_prometheus.db.backends.postgresql"
|
|
DATABASES = {
|
|
"default": {
|
|
"ENGINE": "authentik.root.db",
|
|
"HOST": CONFIG.get("postgresql.host"),
|
|
"NAME": CONFIG.get("postgresql.name"),
|
|
"USER": CONFIG.get("postgresql.user"),
|
|
"PASSWORD": CONFIG.get("postgresql.password"),
|
|
"PORT": CONFIG.get("postgresql.port"),
|
|
"SSLMODE": CONFIG.get("postgresql.sslmode"),
|
|
"SSLROOTCERT": CONFIG.get("postgresql.sslrootcert"),
|
|
"SSLCERT": CONFIG.get("postgresql.sslcert"),
|
|
"SSLKEY": CONFIG.get("postgresql.sslkey"),
|
|
"TEST": {
|
|
"NAME": CONFIG.get("postgresql.test.name"),
|
|
},
|
|
}
|
|
}
|
|
|
|
if CONFIG.get_bool("postgresql.use_pgpool", False):
|
|
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
|
|
|
if CONFIG.get_bool("postgresql.use_pgbouncer", False):
|
|
# https://docs.djangoproject.com/en/4.0/ref/databases/#transaction-pooling-server-side-cursors
|
|
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
|
|
# https://docs.djangoproject.com/en/4.0/ref/databases/#persistent-connections
|
|
DATABASES["default"]["CONN_MAX_AGE"] = None # persistent
|
|
|
|
for replica in CONFIG.get_keys("postgresql.read_replicas"):
|
|
_database = DATABASES["default"].copy()
|
|
for setting in DATABASES["default"].keys():
|
|
default = object()
|
|
if setting in ("TEST",):
|
|
continue
|
|
override = CONFIG.get(
|
|
f"postgresql.read_replicas.{replica}.{setting.lower()}", default=default
|
|
)
|
|
if override is not default:
|
|
_database[setting] = override
|
|
DATABASES[f"replica_{replica}"] = _database
|
|
|
|
DATABASE_ROUTERS = (
|
|
"authentik.tenants.db.FailoverRouter",
|
|
"django_tenants.routers.TenantSyncRouter",
|
|
)
|
|
|
|
# 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"]
|
|
|
|
CELERY = {
|
|
"task_soft_time_limit": 600,
|
|
"worker_max_tasks_per_child": 50,
|
|
"worker_concurrency": CONFIG.get_int("worker.concurrency"),
|
|
"beat_schedule": {
|
|
"clean_expired_models": {
|
|
"task": "authentik.core.tasks.clean_expired_models",
|
|
"schedule": crontab(minute="2-59/5"),
|
|
"options": {"queue": "authentik_scheduled"},
|
|
},
|
|
"user_cleanup": {
|
|
"task": "authentik.core.tasks.clean_temporary_users",
|
|
"schedule": crontab(minute="9-59/5"),
|
|
"options": {"queue": "authentik_scheduled"},
|
|
},
|
|
},
|
|
"beat_scheduler": "authentik.tenants.scheduler:TenantAwarePersistentScheduler",
|
|
"task_create_missing_queues": True,
|
|
"task_default_queue": "authentik",
|
|
"broker_url": CONFIG.get("broker.url") or redis_url(CONFIG.get("redis.db")),
|
|
"result_backend": CONFIG.get("result_backend.url") or redis_url(CONFIG.get("redis.db")),
|
|
"broker_transport_options": CONFIG.get_dict_from_b64_json(
|
|
"broker.transport_options", {"retry_policy": {"timeout": 5.0}}
|
|
),
|
|
"result_backend_transport_options": CONFIG.get_dict_from_b64_json(
|
|
"result_backend.transport_options", {"retry_policy": {"timeout": 5.0}}
|
|
),
|
|
"redis_retry_on_timeout": True,
|
|
}
|
|
|
|
# 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 = "/static/"
|
|
|
|
STORAGES = {
|
|
"staticfiles": {
|
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
|
},
|
|
}
|
|
|
|
|
|
# Media files
|
|
if CONFIG.get("storage.media.backend", "file") == "s3":
|
|
STORAGES["default"] = {
|
|
"BACKEND": "authentik.root.storages.S3Storage",
|
|
"OPTIONS": {
|
|
# How to talk to S3
|
|
"session_profile": CONFIG.get("storage.media.s3.session_profile", None),
|
|
"access_key": CONFIG.get("storage.media.s3.access_key", None),
|
|
"secret_key": CONFIG.get("storage.media.s3.secret_key", None),
|
|
"security_token": CONFIG.get("storage.media.s3.security_token", None),
|
|
"region_name": CONFIG.get("storage.media.s3.region", None),
|
|
"use_ssl": CONFIG.get_bool("storage.media.s3.use_ssl", True),
|
|
"endpoint_url": CONFIG.get("storage.media.s3.endpoint", None),
|
|
"bucket_name": CONFIG.get("storage.media.s3.bucket_name"),
|
|
"default_acl": "private",
|
|
"querystring_auth": True,
|
|
"signature_version": "s3v4",
|
|
"file_overwrite": False,
|
|
"location": "media",
|
|
"url_protocol": (
|
|
"https:" if CONFIG.get("storage.media.s3.secure_urls", True) else "http:"
|
|
),
|
|
"custom_domain": CONFIG.get("storage.media.s3.custom_domain", None),
|
|
},
|
|
}
|
|
# Fallback on file storage backend
|
|
else:
|
|
STORAGES["default"] = {
|
|
"BACKEND": "authentik.root.storages.FileStorage",
|
|
"OPTIONS": {
|
|
"location": Path(CONFIG.get("storage.media.file.path")),
|
|
"base_url": "/media/",
|
|
},
|
|
}
|
|
# Compatibility for apps not supporting top-level STORAGES
|
|
# such as django-tenants
|
|
MEDIA_ROOT = STORAGES["default"]["OPTIONS"]["location"]
|
|
MEDIA_URL = STORAGES["default"]["OPTIONS"]["base_url"]
|
|
|
|
TEST = False
|
|
TEST_RUNNER = "authentik.root.test_runner.PytestTestRunner"
|
|
|
|
structlog_configure()
|
|
LOGGING = get_logger_config()
|
|
|
|
|
|
_DISALLOWED_ITEMS = [
|
|
"SHARED_APPS",
|
|
"TENANT_APPS",
|
|
"INSTALLED_APPS",
|
|
"MIDDLEWARE",
|
|
"AUTHENTICATION_BACKENDS",
|
|
"CELERY",
|
|
]
|
|
|
|
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 _update_settings(app_path: str):
|
|
try:
|
|
settings_module = importlib.import_module(app_path)
|
|
CONFIG.log("debug", "Loaded app settings", path=app_path)
|
|
SHARED_APPS.extend(getattr(settings_module, "SHARED_APPS", []))
|
|
TENANT_APPS.extend(getattr(settings_module, "TENANT_APPS", []))
|
|
MIDDLEWARE.extend(getattr(settings_module, "MIDDLEWARE", []))
|
|
AUTHENTICATION_BACKENDS.extend(getattr(settings_module, "AUTHENTICATION_BACKENDS", []))
|
|
CELERY["beat_schedule"].update(getattr(settings_module, "CELERY_BEAT_SCHEDULE", {}))
|
|
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:
|
|
CELERY["task_always_eager"] = True
|
|
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append(
|
|
"rest_framework.renderers.BrowsableAPIRenderer"
|
|
)
|
|
|
|
TENANT_APPS.append("authentik.core")
|
|
|
|
CONFIG.log("info", "Booting authentik", version=__version__)
|
|
|
|
# Attempt to load enterprise app, if available
|
|
try:
|
|
importlib.import_module("authentik.enterprise.apps")
|
|
CONFIG.log("info", "Enabled authentik enterprise")
|
|
TENANT_APPS.append("authentik.enterprise")
|
|
_update_settings("authentik.enterprise.settings")
|
|
except ImportError:
|
|
pass
|
|
|
|
# Import events after other apps since it relies on tasks and other things from all apps
|
|
# being imported for @prefill_task
|
|
TENANT_APPS.append("authentik.events")
|
|
|
|
|
|
# Load subapps's settings
|
|
for _app in set(SHARED_APPS + TENANT_APPS):
|
|
if not _app.startswith("authentik"):
|
|
continue
|
|
_update_settings(f"{_app}.settings")
|
|
_update_settings("data.user_settings")
|
|
|
|
SHARED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
|
|
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
|