- Move daphne before django.contrib.staticfiles in INSTALLED_APPS so Daphne system check passes and makemigrations --check can run cleanly - Generate tracking/0002: rename auto-generated AuditEvent index names to match Django 5.2 format - Generate accounts/0005: sync AccountUser managers and ManyToMany field help_text to match current Django/guardian defaults The CallSession / CallParticipant / CallEvent migration (training/0004) already existed from the groupcall backend commit; this commit ensures makemigrations --check reports no pending migrations across all apps. Co-Authored-By: Paperclip <noreply@paperclip.ing>
284 lines
7.8 KiB
Python
284 lines
7.8 KiB
Python
from datetime import timedelta
|
|
|
|
import environ
|
|
from pathlib import Path
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
|
|
env = environ.Env()
|
|
|
|
SECRET_KEY = env("DJANGO_SECRET_KEY")
|
|
DEBUG = False
|
|
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[])
|
|
|
|
DJANGO_APPS = [
|
|
"daphne",
|
|
"django.contrib.admin",
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.sessions",
|
|
"django.contrib.messages",
|
|
"django.contrib.staticfiles",
|
|
]
|
|
|
|
THIRD_PARTY_APPS = [
|
|
"channels",
|
|
"rest_framework",
|
|
"rest_framework_simplejwt",
|
|
"django_filters",
|
|
"drf_spectacular",
|
|
"mozilla_django_oidc",
|
|
"guardian",
|
|
"django_celery_results",
|
|
"django_celery_beat",
|
|
]
|
|
|
|
LOCAL_APPS = [
|
|
"api",
|
|
"accounts",
|
|
"courses",
|
|
"cms",
|
|
"tracking",
|
|
"quizzes",
|
|
"training",
|
|
"certificates",
|
|
"reports",
|
|
"notifications",
|
|
"waitlist",
|
|
"attendance",
|
|
]
|
|
|
|
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
|
|
|
AUTH_USER_MODEL = "accounts.AccountUser"
|
|
|
|
MIDDLEWARE = [
|
|
"django.middleware.security.SecurityMiddleware",
|
|
"csp.middleware.CSPMiddleware",
|
|
"api.middleware.SecurityAuditMiddleware",
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"django.middleware.locale.LocaleMiddleware",
|
|
"django.middleware.common.CommonMiddleware",
|
|
"django.middleware.csrf.CsrfViewMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"django.contrib.messages.middleware.MessageMiddleware",
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
]
|
|
|
|
ROOT_URLCONF = "config.urls"
|
|
|
|
TEMPLATES = [
|
|
{
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
"DIRS": [BASE_DIR / "templates"],
|
|
"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",
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
WSGI_APPLICATION = "config.wsgi.application"
|
|
ASGI_APPLICATION = "config.asgi.application"
|
|
|
|
CHANNEL_LAYERS = {
|
|
"default": {
|
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
|
"CONFIG": {
|
|
"hosts": [env("REDIS_URL", default="redis://localhost:6379/0")],
|
|
},
|
|
},
|
|
}
|
|
|
|
DATABASES = {
|
|
"default": env.db("DATABASE_URL"),
|
|
}
|
|
|
|
CACHES = {
|
|
"default": env.cache("REDIS_URL"),
|
|
}
|
|
|
|
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"},
|
|
]
|
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
"accounts.oidc_backend.TrainingOIDCBackend",
|
|
"django.contrib.auth.backends.ModelBackend",
|
|
"guardian.backends.ObjectPermissionBackend",
|
|
]
|
|
|
|
LANGUAGE_CODE = "de-de"
|
|
LANGUAGES = [
|
|
("de", "Deutsch"),
|
|
("en", "English"),
|
|
]
|
|
LOCALE_PATHS = [BASE_DIR / "locale"]
|
|
TIME_ZONE = "Europe/Berlin"
|
|
USE_I18N = True
|
|
USE_L10N = True
|
|
USE_TZ = True
|
|
|
|
STATIC_URL = "/static/"
|
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
|
MEDIA_URL = "/media/"
|
|
MEDIA_ROOT = BASE_DIR / "mediafiles"
|
|
|
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
|
|
# --- DRF ---
|
|
REST_FRAMEWORK = {
|
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
|
"rest_framework.authentication.SessionAuthentication",
|
|
],
|
|
"DEFAULT_PERMISSION_CLASSES": [
|
|
"rest_framework.permissions.IsAuthenticated",
|
|
],
|
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
|
"PAGE_SIZE": 50,
|
|
"DEFAULT_FILTER_BACKENDS": [
|
|
"django_filters.rest_framework.DjangoFilterBackend",
|
|
"rest_framework.filters.OrderingFilter",
|
|
"rest_framework.filters.SearchFilter",
|
|
],
|
|
"EXCEPTION_HANDLER": "api.exceptions.custom_exception_handler",
|
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
|
}
|
|
|
|
# --- drf-spectacular ---
|
|
SPECTACULAR_SETTINGS = {
|
|
"TITLE": "Trainingssoftware API",
|
|
"VERSION": "1.0.0",
|
|
"SERVE_INCLUDE_SCHEMA": False,
|
|
"SCHEMA_PATH_PREFIX": r"/api/v[0-9]+",
|
|
"COMPONENT_SPLIT_REQUEST": True,
|
|
"POSTPROCESSING_HOOKS": [
|
|
"drf_spectacular.hooks.postprocess_schema_enums",
|
|
],
|
|
}
|
|
|
|
# --- Celery ---
|
|
CELERY_BROKER_URL = env("REDIS_URL")
|
|
CELERY_RESULT_BACKEND = "django-db"
|
|
CELERY_CACHE_BACKEND = "default"
|
|
CELERY_ACCEPT_CONTENT = ["json"]
|
|
CELERY_TASK_SERIALIZER = "json"
|
|
CELERY_RESULT_SERIALIZER = "json"
|
|
CELERY_TIMEZONE = TIME_ZONE
|
|
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
|
|
|
# --- OIDC ---
|
|
OIDC_RP_CLIENT_ID = env("OIDC_RP_CLIENT_ID", default="")
|
|
OIDC_RP_CLIENT_SECRET = env("OIDC_RP_CLIENT_SECRET", default="")
|
|
OIDC_OP_AUTHORIZATION_ENDPOINT = env("OIDC_OP_AUTHORIZATION_ENDPOINT", default="")
|
|
OIDC_OP_TOKEN_ENDPOINT = env("OIDC_OP_TOKEN_ENDPOINT", default="")
|
|
OIDC_OP_USER_ENDPOINT = env("OIDC_OP_USER_ENDPOINT", default="")
|
|
OIDC_OP_JWKS_ENDPOINT = env("OIDC_OP_JWKS_ENDPOINT", default="")
|
|
OIDC_RP_SIGN_ALGO = env("OIDC_RP_SIGN_ALGO", default="RS256")
|
|
OIDC_RP_SCOPES = "openid email profile groups"
|
|
OIDC_STORE_ACCESS_TOKEN = False
|
|
OIDC_STORE_REFRESH_TOKEN = False
|
|
OIDC_USERNAME_ALGO = "accounts.oidc_backend.generate_username"
|
|
|
|
# --- SimpleJWT ---
|
|
SIMPLE_JWT = {
|
|
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
|
|
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
|
|
"ROTATE_REFRESH_TOKENS": True,
|
|
"ALGORITHM": "HS256",
|
|
"AUTH_HEADER_TYPES": ("Bearer",),
|
|
}
|
|
|
|
# --- guardian ---
|
|
ANONYMOUS_USER_NAME = None
|
|
|
|
# --- Security Headers ---
|
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
|
X_FRAME_OPTIONS = "DENY"
|
|
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
|
|
|
|
# --- Content Security Policy (django-csp 3.x) ---
|
|
CSP_DEFAULT_SRC = ("'self'",)
|
|
CSP_SCRIPT_SRC = ("'self'",)
|
|
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
|
|
CSP_IMG_SRC = ("'self'", "data:")
|
|
CSP_FONT_SRC = ("'self'",)
|
|
CSP_CONNECT_SRC = ("'self'",)
|
|
CSP_FRAME_SRC = ("'none'",)
|
|
CSP_OBJECT_SRC = ("'none'",)
|
|
CSP_BASE_URI = ("'none'",)
|
|
CSP_FORM_ACTION = ("'self'",)
|
|
CSP_INCLUDE_NONCE_IN = ["script-src"]
|
|
|
|
# --- DRF Throttling ---
|
|
REST_FRAMEWORK["DEFAULT_THROTTLE_CLASSES"] = [
|
|
"rest_framework.throttling.AnonRateThrottle",
|
|
"rest_framework.throttling.UserRateThrottle",
|
|
]
|
|
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
|
|
"anon": "100/hour",
|
|
"user": "2000/hour",
|
|
"login": "10/minute",
|
|
"upload": "30/hour",
|
|
"abuse": "20/minute",
|
|
"audit": "60/minute",
|
|
}
|
|
|
|
# --- Audit Log Retention (days per event class) ---
|
|
AUDIT_RETENTION_DAYS = {
|
|
"auth": 365 * 2,
|
|
"progress": 365 * 3,
|
|
"assessment": 365 * 3,
|
|
"trainer": 365 * 5,
|
|
"certificate": 365 * 10,
|
|
"access": 365,
|
|
"_default": 365,
|
|
}
|
|
|
|
# --- File Upload Policy ---
|
|
MAX_UPLOAD_SIZE_BYTES = env.int("MAX_UPLOAD_SIZE_MB", default=100) * 1024 * 1024
|
|
ALLOWED_UPLOAD_EXTENSIONS = env.list(
|
|
"ALLOWED_UPLOAD_EXTENSIONS",
|
|
default=[
|
|
".pdf", ".jpg", ".jpeg", ".png", ".gif",
|
|
".mp4", ".webm", ".avi", ".mov",
|
|
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
|
|
".csv", ".txt",
|
|
],
|
|
)
|
|
|
|
# --- Security Audit Logging ---
|
|
LOGGING = {
|
|
"version": 1,
|
|
"disable_existing_loggers": False,
|
|
"formatters": {
|
|
"security": {
|
|
"format": "%(asctime)s [SECURITY] %(levelname)s %(message)s",
|
|
},
|
|
},
|
|
"handlers": {
|
|
"console": {"class": "logging.StreamHandler"},
|
|
"security_console": {
|
|
"class": "logging.StreamHandler",
|
|
"formatter": "security",
|
|
},
|
|
},
|
|
"loggers": {
|
|
"security": {
|
|
"handlers": ["security_console"],
|
|
"level": "WARNING",
|
|
"propagate": False,
|
|
},
|
|
},
|
|
"root": {"handlers": ["console"], "level": "INFO"},
|
|
}
|