LDAPS directory adapter with certificate verification, allow-listed reads and writes, escaped filters, password verification and change, password policy and account state, plus an in-memory fake. Adds the LDAP_* configuration, python-ldap with its image libraries, the CA file mount and an opt-in live test for the test OU. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
117 lines
3.6 KiB
Python
117 lines
3.6 KiB
Python
"""Django settings. All deployment values come from environment variables (AD-13)."""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from .env import ConfigError, load_config
|
|
from .logging import build_logging
|
|
|
|
try:
|
|
_config = load_config(os.environ)
|
|
except ConfigError as exc:
|
|
# One clear message, no traceback, no values.
|
|
print(str(exc), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
|
|
SECRET_KEY = _config.secret_key
|
|
DEBUG = _config.debug
|
|
ALLOWED_HOSTS = _config.allowed_hosts
|
|
# Reserved for the client-IP rule in a later entry; currently has no effect.
|
|
TRUSTED_PROXIES = _config.trusted_proxies
|
|
DATA_DIR = _config.data_dir
|
|
DISPLAY_TIME_ZONE = _config.default_timezone
|
|
DIRECTORY = _config.directory
|
|
|
|
INSTALLED_APPS = [
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.auth",
|
|
"django.contrib.sessions",
|
|
"django.contrib.staticfiles",
|
|
"portal.audit",
|
|
"portal.accounts",
|
|
]
|
|
|
|
MIDDLEWARE = [
|
|
"portal.config.middleware.RequestIdMiddleware",
|
|
"portal.config.middleware.SecurityHeadersMiddleware",
|
|
"django.middleware.security.SecurityMiddleware",
|
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"portal.accounts.middleware.AbsoluteSessionLimitMiddleware",
|
|
"django.middleware.locale.LocaleMiddleware",
|
|
"django.middleware.common.CommonMiddleware",
|
|
"django.middleware.csrf.CsrfViewMiddleware",
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
]
|
|
|
|
ROOT_URLCONF = "portal.config.urls"
|
|
WSGI_APPLICATION = "portal.config.wsgi.application"
|
|
|
|
TEMPLATES = [
|
|
{
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
"DIRS": [BASE_DIR / "portal" / "templates"],
|
|
"APP_DIRS": True,
|
|
"OPTIONS": {
|
|
"context_processors": [
|
|
"django.template.context_processors.request",
|
|
"django.template.context_processors.i18n",
|
|
],
|
|
},
|
|
}
|
|
]
|
|
|
|
DATABASES = {
|
|
"default": {
|
|
"ENGINE": "django.db.backends.sqlite3",
|
|
"NAME": DATA_DIR / "db.sqlite3",
|
|
"OPTIONS": {
|
|
"transaction_mode": "IMMEDIATE",
|
|
"timeout": 20,
|
|
"init_command": "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;",
|
|
},
|
|
}
|
|
}
|
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
|
|
# Time and language (AD-15): store UTC, translate every string.
|
|
USE_TZ = True
|
|
TIME_ZONE = "UTC"
|
|
USE_I18N = True
|
|
LANGUAGE_CODE = "en"
|
|
LANGUAGES = [("en", "English"), ("de", "Deutsch")]
|
|
LOCALE_PATHS = [BASE_DIR / "portal" / "locale"]
|
|
|
|
# Static files served by whitenoise; assets are built into the image.
|
|
STATIC_URL = "static/"
|
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
|
STATICFILES_DIRS = [BASE_DIR / "portal" / "static"]
|
|
STORAGES = {
|
|
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
|
|
}
|
|
|
|
# Sessions in the database; cookies secure, HttpOnly, SameSite=Lax (AD-6).
|
|
SESSION_ENGINE = "portal.accounts.session_store"
|
|
SESSION_COOKIE_AGE = 60 * 60 # idle limit: 1 hour, renewed on every request
|
|
SESSION_SAVE_EVERY_REQUEST = True
|
|
SESSION_ABSOLUTE_SECONDS = 4 * 60 * 60 # absolute limit: 4 hours
|
|
SESSION_COOKIE_SECURE = not DEBUG
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = "Lax"
|
|
CSRF_COOKIE_SECURE = not DEBUG
|
|
CSRF_COOKIE_HTTPONLY = True
|
|
CSRF_COOKIE_SAMESITE = "Lax"
|
|
|
|
# TLS ends at the reverse proxy.
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
|
SECURE_REFERRER_POLICY = "same-origin"
|
|
SECURE_HSTS_SECONDS = 0 if DEBUG else 31536000
|
|
X_FRAME_OPTIONS = "DENY"
|
|
|
|
LOGGING = build_logging(_config.log_level)
|