Files
meet/src/backend/meet/settings.py
T
Paul Csiki cf3960db95 (backend) add Traefik reverse proxy support for media-auth
Adds support for serving media behind Traefik, which currently cannot work
at all.

The media-auth subrequest views read the original request URL from a
hardcoded HTTP_X_ORIGINAL_URL header. That header is an nginx-ingress
convention. Traefik's ForwardAuth middleware sends X-Forwarded-Uri instead
and has no mechanism to emit X-Original-URL, so behind Traefik every
recording download and file attachment is rejected with a bare 403 --
indistinguishable from a legitimate permission denial, which makes it
painful to diagnose.

Add MEDIA_AUTH_ORIGINAL_URL_HEADER, defaulting to HTTP_X_ORIGINAL_URL so
existing nginx-ingress deployments are unaffected. Traefik deployments set
it to HTTP_X_FORWARDED_URI. It is used in both places that resolve the
header: RecordingViewSet._auth_get_original_url and the file attachment
_authorize_subrequest. The log message on a missing header now names the
header actually expected, which is what makes the failure diagnosable.

This mirrors the setting the sibling Docs project already exposes
(suitenumerique/docs, MEDIA_AUTH_ORIGINAL_URL_HEADER) for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 18:07:23 +02:00

1429 lines
49 KiB
Python
Executable File

"""
Django settings for Meet project.
Generated by 'django-admin startproject' using Django 3.1.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
# pylint: disable=too-many-lines
import json
import warnings
from os import path
from socket import gethostbyname, gethostname
from django.utils.translation import gettext_lazy as _
import dj_database_url
import sentry_sdk
from configurations import Configuration, values
from lasuite.configuration.values import SecretFileValue
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import ignore_logger
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = path.dirname(path.dirname(path.abspath(__file__)))
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
def get_release():
"""
Get the current release of the application
By release, we mean the release from the version.json file à la Mozilla [1]
(if any). If this file has not been found, it defaults to "NA".
[1]
https://github.com/mozilla-services/Dockerflow/blob/master/docs/version_object.md
"""
# Try to get the current release from the version.json file generated by the
# CI during the Docker image build
try:
with open(path.join(BASE_DIR, "version.json"), encoding="utf8") as version:
return json.load(version)["version"]
except FileNotFoundError:
return "NA" # Default: not available
class Base(Configuration):
"""
This is the base configuration every configuration (aka environment) should inherit from. It
is recommended to configure third-party applications by creating a configuration mixins in
./configurations and compose the Base configuration with those mixins.
It depends on an environment variable that SHOULD be defined:
* DJANGO_SECRET_KEY
You may also want to override default configuration by setting the following environment
variables:
* DJANGO_SENTRY_DSN
* DB_NAME
* DB_HOST
* DB_PASSWORD
* DB_USER
"""
DEBUG = False
USE_SWAGGER = False
API_VERSION = "v1.0"
EXTERNAL_API_VERSION = "v1.0"
EXTERNAL_API_ENABLED = values.BooleanValue(
False, environ_name="EXTERNAL_API_ENABLED", environ_prefix=None
)
DATA_DIR = values.Value(path.join("/", "data"), environ_name="DATA_DIR")
# Security
ALLOWED_HOSTS = values.ListValue([])
SECRET_KEY = SecretFileValue(None)
SILENCED_SYSTEM_CHECKS = values.ListValue([])
ALLOW_UNSECURE_USER_LISTING = values.BooleanValue(
False, environ_name="ALLOW_UNSECURE_USER_LISTING", environ_prefix=None
)
# Application definition
ROOT_URLCONF = "meet.urls"
WSGI_APPLICATION = "meet.wsgi.application"
# Database
DATABASES = {
"default": dj_database_url.config()
if values.DatabaseURLValue(
None, environ_name="DATABASE_URL", environ_prefix=None
)
else {
"ENGINE": values.Value(
"django.db.backends.postgresql_psycopg2",
environ_name="DB_ENGINE",
environ_prefix=None,
),
"NAME": values.Value("meet", environ_name="DB_NAME", environ_prefix=None),
"USER": values.Value("dinum", environ_name="DB_USER", environ_prefix=None),
"PASSWORD": SecretFileValue(
"pass", environ_name="DB_PASSWORD", environ_prefix=None
),
"HOST": values.Value(
"localhost", environ_name="DB_HOST", environ_prefix=None
),
"PORT": values.Value(5432, environ_name="DB_PORT", environ_prefix=None),
}
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Static files (CSS, JavaScript, Images)
STATIC_URL = "/static/"
STATIC_ROOT = path.join(DATA_DIR, "static")
MEDIA_URL = "/media/"
MEDIA_ROOT = path.join(DATA_DIR, "media")
MEDIA_BASE_URL = values.Value(
"", environ_name="MEDIA_BASE_URL", environ_prefix=None
)
# Header the reverse proxy uses to pass the original request URL to the
# media-auth subrequest views. nginx-ingress sends X-Original-URL, which is
# the default. Other proxies use different headers -- Traefik's ForwardAuth,
# for instance, sends X-Forwarded-Uri and cannot emit X-Original-URL at all.
MEDIA_AUTH_ORIGINAL_URL_HEADER = values.Value(
default="HTTP_X_ORIGINAL_URL",
environ_name="MEDIA_AUTH_ORIGINAL_URL_HEADER",
environ_prefix=None,
)
SITE_ID = 1
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
},
"staticfiles": {
"BACKEND": values.Value(
"whitenoise.storage.CompressedManifestStaticFilesStorage",
environ_name="STORAGES_STATICFILES_BACKEND",
),
},
}
# Media
AWS_S3_ENDPOINT_URL = values.Value(
environ_name="AWS_S3_ENDPOINT_URL", environ_prefix=None
)
AWS_S3_ACCESS_KEY_ID = SecretFileValue(
environ_name="AWS_S3_ACCESS_KEY_ID", environ_prefix=None
)
AWS_S3_SECRET_ACCESS_KEY = SecretFileValue(
environ_name="AWS_S3_SECRET_ACCESS_KEY", environ_prefix=None
)
AWS_S3_REGION_NAME = values.Value(
environ_name="AWS_S3_REGION_NAME", environ_prefix=None
)
AWS_STORAGE_BUCKET_NAME = values.Value(
"meet-media-storage",
environ_name="AWS_STORAGE_BUCKET_NAME",
environ_prefix=None,
)
AWS_S3_SIGNATURE_VERSION = values.Value(
"s3v4",
environ_name="AWS_S3_SIGNATURE_VERSION",
environ_prefix=None,
)
AWS_S3_UPLOAD_POLICY_EXPIRATION = values.Value(
60, # 1 minute
environ_name="AWS_S3_UPLOAD_POLICY_EXPIRATION",
environ_prefix=None,
)
AWS_S3_DOMAIN_REPLACE = values.Value(
environ_name="AWS_S3_DOMAIN_REPLACE",
environ_prefix=None,
)
FILE_UPLOAD_ENABLED = values.BooleanValue(
# False to avoid a breaking change for now
default=False,
environ_name="FILE_UPLOAD_ENABLED",
environ_prefix=None,
)
FILE_PURGE_GRACE_DAYS = values.PositiveIntegerValue(
default=7, environ_name="FILE_PURGE_GRACE_DAYS", environ_prefix=None
)
FILE_UPLOAD_PATH = values.Value(
"files", environ_name="FILE_UPLOAD_PATH", environ_prefix=None
)
FILE_UPLOAD_TMP_PATH = values.Value(
"tmp/files", environ_name="FILE_UPLOAD_TMP_PATH", environ_prefix=None
)
FILE_UPLOAD_APPLY_RESTRICTIONS = values.BooleanValue(
default=True, environ_name="FILE_UPLOAD_APPLY_RESTRICTIONS", environ_prefix=None
)
FILE_UPLOAD_RESTRICTIONS = values.DictValue(
{
"background_image": {
"max_size": 2 * MB,
"max_count_by_user": 10,
"allowed_extensions": [".jpeg", ".jpg", ".png"],
"allowed_mimetypes": ["image/jpeg", "image/png"],
},
},
environ_name="FILE_UPLOAD_RESTRICTIONS",
environ_prefix=None,
)
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
# Languages
LANGUAGE_CODE = values.Value("en-us")
DRF_NESTED_MULTIPART_PARSER = {
# output of parser is converted to querydict
# if is set to False, dict python is returned
"querydict": False,
}
# Careful! Languages should be ordered by priority, as this tuple is used to get
# fallback/default languages throughout the app.
LANGUAGES = values.SingleNestedTupleValue(
(
("en-us", _("English")),
("fr-fr", _("French")),
("nl-nl", _("Dutch")),
("de-de", _("German")),
("es-es", _("Spanish")),
)
)
LOCALE_PATHS = (path.join(BASE_DIR, "locale"),)
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Templates
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [path.join(BASE_DIR, "templates")],
"OPTIONS": {
"context_processors": [
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.csrf",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.request",
"django.template.context_processors.tz",
],
"loaders": [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
},
},
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"dockerflow.django.middleware.DockerflowMiddleware",
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"core.authentication.backends.OIDCAuthenticationBackend",
]
# Django applications from the highest priority to the lowest
INSTALLED_APPS = [
# Meet
"core",
"demo",
"drf_spectacular",
# Third party apps
"corsheaders",
"dockerflow.django",
"rest_framework",
"parler",
"easy_thumbnails",
# Django
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.postgres",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# OIDC third party
"mozilla_django_oidc",
]
# Cache
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": values.Value(
"redis://redis:6379/1",
environ_name="REDIS_URL",
environ_prefix=None,
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"core.authentication.backends.SessionAuthenticationWith401",
),
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
"nested_multipart_parser.drf.DrfNestedParser",
],
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
],
"EXCEPTION_HANDLER": "core.api.exception_handler",
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_THROTTLE_RATES": {
"request_entry": values.Value(
default="150/minute",
environ_name="REQUEST_ENTRY_THROTTLE_RATES",
environ_prefix=None,
),
"creation_callback": values.Value(
default="600/minute",
environ_name="CREATION_CALLBACK_THROTTLE_RATES",
environ_prefix=None,
),
"roomkit_join": values.Value(
default="300/minute",
environ_name="ROOMKIT_JOIN_THROTTLE_RATES",
environ_prefix=None,
),
"connection_test": values.Value(
default="30/minute",
environ_name="CONNECTION_TEST_THROTTLE_RATES",
environ_prefix=None,
),
},
}
MONITORED_THROTTLE_FAILURE_CALLBACK = (
"core.api.throttling.sentry_monitoring_throttle_failure"
)
SPECTACULAR_SETTINGS = {
"TITLE": "Meet API",
"DESCRIPTION": "This is the Meet API schema.",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
"ENABLE_DJANGO_DEPLOY_CHECK": values.BooleanValue(
default=False,
environ_name="SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK",
),
"COMPONENT_SPLIT_REQUEST": True,
# OTHER SETTINGS
"SWAGGER_UI_DIST": "SIDECAR", # shorthand to use the sidecar instead
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
}
# Frontend
FRONTEND_CONFIGURATION = {
# If set, a <link> tag with this URL as href is added to the <head> of the frontend app.
# This is useful if you want to change CSS variables to customize the look of the app.
"custom_css_url": values.Value(
None, environ_name="FRONTEND_CUSTOM_CSS_URL", environ_prefix=None
),
"analytics": values.DictValue(
{}, environ_name="FRONTEND_ANALYTICS", environ_prefix=None
),
"support": values.DictValue(
{}, environ_name="FRONTEND_SUPPORT", environ_prefix=None
),
"silence_livekit_debug_logs": values.BooleanValue(
False, environ_name="FRONTEND_SILENCE_LIVEKIT_DEBUG", environ_prefix=None
),
"is_silent_login_enabled": values.BooleanValue(
True, environ_name="FRONTEND_IS_SILENT_LOGIN_ENABLED", environ_prefix=None
),
"idle_disconnect_warning_delay": values.PositiveIntegerValue(
None,
environ_name="FRONTEND_IDLE_DISCONNECT_WARNING_DELAY",
environ_prefix=None,
),
"feedback": values.DictValue(
{}, environ_name="FRONTEND_FEEDBACK", environ_prefix=None
),
"documentation_url": values.Value(
None, environ_name="FRONTEND_DOCUMENTATION_URL", environ_prefix=None
),
"external_home_url": values.Value(
None, environ_name="FRONTEND_EXTERNAL_HOME_URL", environ_prefix=None
),
"use_french_gov_footer": values.BooleanValue(
False, environ_name="FRONTEND_USE_FRENCH_GOV_FOOTER", environ_prefix=None
),
"use_proconnect_button": values.BooleanValue(
False, environ_name="FRONTEND_USE_PROCONNECT_BUTTON", environ_prefix=None
),
"manifest_link": values.Value(
None, environ_name="FRONTEND_MANIFEST_LINK", environ_prefix=None
),
"transcription_destination": values.Value(
None, environ_name="FRONTEND_TRANSCRIPTION_DESTINATION", environ_prefix=None
),
"max_participants_for_sound": values.PositiveIntegerValue(
5, environ_name="FRONTEND_MAX_PARTICIPANTS_FOR_SOUND", environ_prefix=None
),
"auto_mute_on_join_threshold": values.PositiveIntegerValue(
50, environ_name="FRONTEND_AUTO_MUTE_ON_JOIN_THRESHOLD", environ_prefix=None
),
}
# Mail
EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend")
EMAIL_HOST = values.Value(None)
EMAIL_HOST_USER = values.Value(None)
EMAIL_HOST_PASSWORD = SecretFileValue(None)
EMAIL_PORT = values.PositiveIntegerValue(None)
EMAIL_USE_TLS = values.BooleanValue(False)
EMAIL_USE_SSL = values.BooleanValue(False)
EMAIL_FROM = values.Value("from@example.com")
EMAIL_BRAND_NAME = values.Value(None)
EMAIL_SUPPORT_EMAIL = values.Value(None)
EMAIL_LOGO_IMG = values.Value(None)
EMAIL_DOMAIN = values.Value(None)
EMAIL_APP_BASE_URL = values.Value(None)
AUTH_USER_MODEL = "core.User"
# CORS
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False)
CORS_ALLOWED_ORIGINS = values.ListValue([])
CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([])
# Sentry
SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN")
# Easy thumbnails
THUMBNAIL_EXTENSION = "webp"
THUMBNAIL_TRANSPARENCY_EXTENSION = "webp"
THUMBNAIL_DEFAULT_STORAGE_ALIAS = "default"
THUMBNAIL_ALIASES = {}
# Celery
# Defaults to False to avoid breaking change, async task will be run
# synchronously
CELERY_ENABLED = values.BooleanValue(False, environ_prefix=None)
CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(False, environ_prefix=None)
CELERY_TASK_DEFAULT_QUEUE = values.Value("meet-backend", environ_prefix=None)
CELERY_BROKER_URL = values.Value("redis://redis:6379/0", environ_prefix=None)
CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({}, environ_prefix=None)
# Session
SESSION_ENGINE = values.Value(
default="django.contrib.sessions.backends.cache",
environ_name="SESSION_ENGINE",
environ_prefix=None,
)
SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_AGE = values.PositiveIntegerValue(
default=60 * 60 * 12, environ_name="SESSION_COOKIE_AGE", environ_prefix=None
)
# OIDC - Authorization Code Flow
OIDC_AUTHENTICATE_CLASS = "lasuite.oidc_login.views.OIDCAuthenticationRequestView"
OIDC_CALLBACK_CLASS = "lasuite.oidc_login.views.OIDCAuthenticationCallbackView"
OIDC_CREATE_USER = values.BooleanValue(
default=True, environ_name="OIDC_CREATE_USER", environ_prefix=None
)
OIDC_VERIFY_SSL = values.BooleanValue(
default=True, environ_name="OIDC_VERIFY_SSL", environ_prefix=None
)
OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = values.BooleanValue(
default=False,
environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION",
environ_prefix=None,
)
OIDC_USER_SUB_FIELD_IMMUTABLE = values.BooleanValue(
default=True, environ_name="OIDC_USER_SUB_FIELD_IMMUTABLE", environ_prefix=None
)
OIDC_TIMEOUT = values.IntegerValue(
5, environ_name="OIDC_TIMEOUT", environ_prefix=None
)
OIDC_PROXY = values.Value(None, environ_name="OIDC_PROXY", environ_prefix=None)
OIDC_RP_SIGN_ALGO = values.Value(
"RS256", environ_name="OIDC_RP_SIGN_ALGO", environ_prefix=None
)
OIDC_RP_CLIENT_ID = values.Value(
"meet", environ_name="OIDC_RP_CLIENT_ID", environ_prefix=None
)
OIDC_RP_CLIENT_SECRET = SecretFileValue(
None,
environ_name="OIDC_RP_CLIENT_SECRET",
environ_prefix=None,
)
OIDC_OP_JWKS_ENDPOINT = values.Value(
environ_name="OIDC_OP_JWKS_ENDPOINT", environ_prefix=None
)
OIDC_OP_AUTHORIZATION_ENDPOINT = values.Value(
environ_name="OIDC_OP_AUTHORIZATION_ENDPOINT", environ_prefix=None
)
OIDC_OP_TOKEN_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_TOKEN_ENDPOINT", environ_prefix=None
)
OIDC_OP_USER_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_USER_ENDPOINT", environ_prefix=None
)
OIDC_OP_INTROSPECTION_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_INTROSPECTION_ENDPOINT", environ_prefix=None
)
OIDC_OP_USER_ENDPOINT_FORMAT = values.Value(
"AUTO", environ_name="OIDC_OP_USER_ENDPOINT_FORMAT", environ_prefix=None
)
OIDC_OP_LOGOUT_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_LOGOUT_ENDPOINT", environ_prefix=None
)
OIDC_OP_URL = values.Value(None, environ_name="OIDC_OP_URL", environ_prefix=None)
OIDC_AUTH_REQUEST_EXTRA_PARAMS = values.DictValue(
{}, environ_name="OIDC_AUTH_REQUEST_EXTRA_PARAMS", environ_prefix=None
)
OIDC_RP_SCOPES = values.Value(
"openid email", environ_name="OIDC_RP_SCOPES", environ_prefix=None
)
OIDC_USE_PKCE = values.BooleanValue(
default=False, environ_name="OIDC_USE_PKCE", environ_prefix=None
)
OIDC_PKCE_CODE_CHALLENGE_METHOD = values.Value(
default="S256",
environ_name="OIDC_PKCE_CODE_CHALLENGE_METHOD",
environ_prefix=None,
)
OIDC_PKCE_CODE_VERIFIER_SIZE = values.IntegerValue(
default=64, environ_name="OIDC_PKCE_CODE_VERIFIER_SIZE", environ_prefix=None
)
LOGIN_REDIRECT_URL = values.Value(
None, environ_name="LOGIN_REDIRECT_URL", environ_prefix=None
)
LOGIN_REDIRECT_URL_FAILURE = values.Value(
None, environ_name="LOGIN_REDIRECT_URL_FAILURE", environ_prefix=None
)
LOGOUT_REDIRECT_URL = values.Value(
None, environ_name="LOGOUT_REDIRECT_URL", environ_prefix=None
)
OIDC_USE_NONCE = values.BooleanValue(
default=True, environ_name="OIDC_USE_NONCE", environ_prefix=None
)
OIDC_REDIRECT_REQUIRE_HTTPS = values.BooleanValue(
default=False, environ_name="OIDC_REDIRECT_REQUIRE_HTTPS", environ_prefix=None
)
OIDC_REDIRECT_ALLOWED_HOSTS = values.ListValue(
default=[], environ_name="OIDC_REDIRECT_ALLOWED_HOSTS", environ_prefix=None
)
OIDC_STORE_ID_TOKEN = values.BooleanValue(
default=True, environ_name="OIDC_STORE_ID_TOKEN", environ_prefix=None
)
ALLOW_LOGOUT_GET_METHOD = values.BooleanValue(
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
)
OIDC_REDIRECT_FIELD_NAME = values.Value(
"returnTo", environ_name="OIDC_REDIRECT_FIELD_NAME", environ_prefix=None
)
OIDC_USERINFO_FULLNAME_FIELDS = values.ListValue(
default=["given_name", "usual_name"],
environ_name="OIDC_USERINFO_FULLNAME_FIELDS",
environ_prefix=None,
)
OIDC_USERINFO_SHORTNAME_FIELD = values.Value(
default="given_name",
environ_name="OIDC_USERINFO_SHORTNAME_FIELD",
environ_prefix=None,
)
OIDC_USERINFO_ESSENTIAL_CLAIMS = values.ListValue(
default=[],
environ_name="OIDC_USERINFO_ESSENTIAL_CLAIMS",
environ_prefix=None,
)
# OIDC Resource Server Backend
OIDC_RS_BACKEND_CLASS = "core.external_api.authentication.ResourceServerBackend"
OIDC_RS_CLIENT_ID = values.Value(
"meet", environ_name="OIDC_RS_CLIENT_ID", environ_prefix=None
)
OIDC_RS_CLIENT_SECRET = SecretFileValue(
None,
environ_name="OIDC_RS_CLIENT_SECRET",
environ_prefix=None,
)
OIDC_RS_AUDIENCE_CLAIM = values.Value(
default="client_id", environ_name="OIDC_RS_AUDIENCE_CLAIM", environ_prefix=None
)
OIDC_RS_ENCRYPTION_ENCODING = values.Value(
default="A256GCM",
environ_name="OIDC_RS_ENCRYPTION_ENCODING",
environ_prefix=None,
)
OIDC_RS_ENCRYPTION_ALGO = values.Value(
default="RSA-OAEP", environ_name="OIDC_RS_ENCRYPTION_ALGO", environ_prefix=None
)
OIDC_RS_SIGNING_ALGO = values.Value(
default="ES256", environ_name="OIDC_RS_SIGNING_ALGO", environ_prefix=None
)
OIDC_RS_SCOPES = values.ListValue(
default=["lasuite_meet"],
environ_name="OIDC_RS_SCOPES",
environ_prefix=None,
)
OIDC_RS_PRIVATE_KEY_STR = SecretFileValue(
environ_name="OIDC_RS_PRIVATE_KEY_STR", environ_prefix=None
)
OIDC_RS_ENCRYPTION_KEY_TYPE = values.Value(
default="RSA", environ_name="OIDC_RS_ENCRYPTION_KEY_TYPE", environ_prefix=None
)
OIDC_RS_SCOPES_PREFIX = values.Value(
default=None, environ_name="OIDC_RS_SCOPES_PREFIX", environ_prefix=None
)
# Video conference configuration
LIVEKIT_CONFIGURATION = {
"api_key": SecretFileValue(environ_name="LIVEKIT_API_KEY", environ_prefix=None),
"api_secret": SecretFileValue(
environ_name="LIVEKIT_API_SECRET", environ_prefix=None
),
"url": values.Value(environ_name="LIVEKIT_API_URL", environ_prefix=None),
}
LIVEKIT_FORCE_WSS_PROTOCOL = values.BooleanValue(
False, environ_name="LIVEKIT_FORCE_WSS_PROTOCOL", environ_prefix=None
)
LIVEKIT_DEFAULT_SOURCES = values.ListValue(
[
"camera",
"microphone",
"screen_share",
"screen_share_audio",
],
environ_name="LIVEKIT_DEFAULT_SOURCES",
environ_prefix=None,
)
LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND = values.BooleanValue(
environ_name="LIVEKIT_ENABLE_FIREFOX_PROXY_WORKAROUND",
environ_prefix=None,
default=False,
)
CONNECTION_TEST_ENABLED = values.BooleanValue(
environ_name="CONNECTION_TEST_ENABLED",
environ_prefix=None,
default=False,
)
CONNECTION_TEST_TOKEN_TTL_SECONDS = values.PositiveIntegerValue(
300,
environ_name="CONNECTION_TEST_TOKEN_TTL_SECONDS",
environ_prefix=None,
)
# The effective room max age is always computed as
# CONNECTION_TEST_TOKEN_TTL_SECONDS + this value. Token expiration does
# not automatically delete rooms, so once that age is reached, the
# cleanup worker will explicitly delete the room if it still exists.
CONNECTION_TEST_ROOM_EXTRA_AGE_SECONDS = values.PositiveIntegerValue(
10,
environ_name="CONNECTION_TEST_ROOM_EXTRA_AGE_SECONDS",
environ_prefix=None,
)
CONNECTION_TEST_ROOM_PREFIX = values.Value(
"connection-test",
environ_name="CONNECTION_TEST_ROOM_PREFIX",
environ_prefix=None,
)
LIVEKIT_VERIFY_SSL = values.BooleanValue(
True, environ_name="LIVEKIT_VERIFY_SSL", environ_prefix=None
)
# Regex to filter webhook events by room name. Only matching events are processed.
LIVEKIT_WEBHOOK_EVENTS_FILTER_REGEX = values.Value(
None, environ_name="LIVEKIT_WEBHOOK_EVENTS_FILTER_REGEX", environ_prefix=None
)
RESOURCE_DEFAULT_ACCESS_LEVEL = values.Value(
"public", environ_name="RESOURCE_DEFAULT_ACCESS_LEVEL", environ_prefix=None
)
ALLOW_UNREGISTERED_ROOMS = values.BooleanValue(
True, environ_name="ALLOW_UNREGISTERED_ROOMS", environ_prefix=None
)
# if provided, treat as suspicious (possible privilege escalation attempt).
PARTICIPANT_FORBIDDEN_PERMISSION_FIELDS = values.ListValue(
["hidden", "recorder", "agent"],
environ_name="PARTICIPANT_FORBIDDEN_PERMISSION_FIELDS",
environ_prefix=None,
)
AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = values.BooleanValue(
True,
environ_name="AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME",
environ_prefix=None,
)
# Recording settings
RECORDING_ENABLE = values.BooleanValue(
False, environ_name="RECORDING_ENABLE", environ_prefix=None
)
RECORDING_OUTPUT_FOLDER = values.Value(
"recordings", environ_name="RECORDING_OUTPUT_FOLDER", environ_prefix=None
)
RECORDING_WORKER_CLASSES = values.DictValue(
{
"screen_recording": "core.recording.worker.services.VideoCompositeEgressService",
"transcript": "core.recording.worker.services.AudioCompositeEgressService",
},
environ_name="RECORDING_WORKER_CLASSES",
environ_prefix=None,
)
# Number of days before recordings expire - must be synced with bucket lifecycle policy
# Set to None for no expiration
RECORDING_EXPIRATION_DAYS = values.IntegerValue(
None, environ_name="RECORDING_EXPIRATION_DAYS", environ_prefix=None
)
# Recording max duration in milliseconds - must be synced with LiveKit Egress configuration
# Set to None for no max duration
RECORDING_MAX_DURATION = values.IntegerValue(
None, environ_name="RECORDING_MAX_DURATION", environ_prefix=None
)
# Recording encoding options for LiveKit Egress (video composite egress only).
# These settings affect screen recordings handled by VideoCompositeEgressService;
# they are silently ignored by AudioCompositeEgressService (audio-only transcript
# recordings), whose request never carries advanced EncodingOptions.
# When disabled, LiveKit falls back to its built-in H264_720P_30 preset
# (1280x720, 30 fps, 3000 kbps H.264 MAIN video, 128 kbps AAC audio).
# When enabled, the values below are passed to LiveKit as EncodingOptions
# (advanced) and replace the preset. Lowering framerate and bitrate reduces
# output file size and CPU load on the egress worker.
RECORDING_ENCODING_ENABLED = values.BooleanValue(
False, environ_name="RECORDING_ENCODING_ENABLED", environ_prefix=None
)
RECORDING_ENCODING_WIDTH = values.PositiveIntegerValue(
1280, environ_name="RECORDING_ENCODING_WIDTH", environ_prefix=None
)
RECORDING_ENCODING_HEIGHT = values.PositiveIntegerValue(
720, environ_name="RECORDING_ENCODING_HEIGHT", environ_prefix=None
)
RECORDING_ENCODING_FRAMERATE = values.PositiveIntegerValue(
30, environ_name="RECORDING_ENCODING_FRAMERATE", environ_prefix=None
)
RECORDING_ENCODING_VIDEO_BITRATE_KBPS = values.PositiveIntegerValue(
3000,
environ_name="RECORDING_ENCODING_VIDEO_BITRATE_KBPS",
environ_prefix=None,
)
RECORDING_ENCODING_AUDIO_BITRATE_KBPS = values.PositiveIntegerValue(
128,
environ_name="RECORDING_ENCODING_AUDIO_BITRATE_KBPS",
environ_prefix=None,
)
RECORDING_ENCODING_KEY_FRAME_INTERVAL_S = values.FloatValue(
4.0,
environ_name="RECORDING_ENCODING_KEY_FRAME_INTERVAL_S",
environ_prefix=None,
)
SUMMARY_SERVICE_VERSION = values.PositiveIntegerValue(
1, environ_name="SUMMARY_SERVICE_VERSION", environ_prefix=None
)
SUMMARY_SERVICE_ENDPOINT = values.Value(
None, environ_name="SUMMARY_SERVICE_ENDPOINT", environ_prefix=None
)
SUMMARY_SERVICE_API_TOKEN = SecretFileValue(
None, environ_name="SUMMARY_SERVICE_API_TOKEN", environ_prefix=None
)
SUMMARY_SERVICE_WEBHOOK_API_TOKEN = SecretFileValue(
None, environ_name="SUMMARY_SERVICE_WEBHOOK_API_TOKEN", environ_prefix=None
)
SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS = (
values.PositiveIntegerValue(
60 * 60 * 24,
environ_name="SUMMARY_SERVICE_CLOUD_STORAGE_SIGNED_URL_EXPIRY_SECONDS",
environ_prefix=None,
)
)
TRANSCRIPTION_SATISFACTION_FORM_BASE_URL = values.Value(
None,
environ_name="TRANSCRIPTION_SATISFACTION_FORM_BASE_URL",
environ_prefix=None,
)
SCREEN_RECORDING_BASE_URL = values.Value(
None, environ_name="SCREEN_RECORDING_BASE_URL", environ_prefix=None
)
RECORDING_DOWNLOAD_BASE_URL = values.Value(
None, environ_name="RECORDING_DOWNLOAD_BASE_URL", environ_prefix=None
)
# Analytics
ANALYTICS_BACKEND = values.Value(
None, environ_name="ANALYTICS_BACKEND", environ_prefix=None
)
ANALYTICS_BACKEND_SETTINGS = values.DictValue(
{}, environ_name="ANALYTICS_BACKEND_SETTINGS", environ_prefix=None
)
# Marketing and communication settings
SIGNUP_NEW_USER_TO_MARKETING_EMAIL = values.BooleanValue(
False, # When enabled, new users are automatically added to mailing list.
environ_name="SIGNUP_NEW_USER_TO_MARKETING_EMAIL",
environ_prefix=None,
)
MARKETING_SERVICE_CLASS = values.Value(
"core.services.marketing.BrevoMarketingService",
environ_name="MARKETING_SERVICE_CLASS",
environ_prefix=None,
)
BREVO_API_KEY = SecretFileValue(
None, environ_name="BREVO_API_KEY", environ_prefix=None
)
BREVO_API_CONTACT_LIST_IDS = values.ListValue(
[],
environ_name="BREVO_API_CONTACT_LIST_IDS",
environ_prefix=None,
converter=int,
)
BREVO_API_CONTACT_ATTRIBUTES = values.DictValue({"VISIO_USER": True})
BREVO_API_TIMEOUT = values.PositiveIntegerValue(
1, environ_name="BREVO_API_TIMEOUT", environ_prefix=None
)
# Lobby configurations
PRESENCE_KEY_PREFIX = values.Value(
"room_presence", environ_name="PRESENCE_KEY_PREFIX", environ_prefix=None
)
PRESENCE_CACHE_TIMEOUT = values.PositiveIntegerValue(
3600, environ_name="PRESENCE_CACHE_TIMEOUT", environ_prefix=None
)
PRESENCE_CLEAR_ON_PARTICIPANT_LEFT = values.BooleanValue(
True, environ_name="PRESENCE_CLEAR_ON_PARTICIPANT_LEFT", environ_prefix=None
)
LOBBY_KEY_PREFIX = values.Value(
"room_lobby", environ_name="LOBBY_KEY_PREFIX", environ_prefix=None
)
LOBBY_WAITING_TIMEOUT = values.PositiveIntegerValue(
3, environ_name="LOBBY_WAITING_TIMEOUT", environ_prefix=None
)
LOBBY_DENIED_TIMEOUT = values.PositiveIntegerValue(
5, environ_name="LOBBY_DENIED_TIMEOUT", environ_prefix=None
)
LOBBY_ACCEPTED_TIMEOUT = values.PositiveIntegerValue(
21600, # 6hrs
environ_name="LOBBY_ACCEPTED_TIMEOUT",
environ_prefix=None,
)
LOBBY_NOTIFICATION_TYPE = values.Value(
"participantWaiting",
environ_name="LOBBY_NOTIFICATION_TYPE",
environ_prefix=None,
)
LOBBY_COOKIE_NAME = values.Value(
"lobbyParticipantId",
environ_name="LOBBY_COOKIE_NAME",
environ_prefix=None,
)
# Calendar integrations
ROOM_CREATION_CALLBACK_CACHE_TIMEOUT = values.PositiveIntegerValue(
600, # 10 minutes
environ_name="ROOM_CREATION_CALLBACK_CACHE_TIMEOUT",
environ_prefix=None,
)
# SIP Telephony
ROOM_TELEPHONY_ENABLED = values.BooleanValue(
False,
environ_name="ROOM_TELEPHONY_ENABLED",
environ_prefix=None,
)
ROOM_TELEPHONY_PIN_LENGTH = values.PositiveIntegerValue(
10,
environ_name="ROOM_TELEPHONY_PIN_LENGTH",
environ_prefix=None,
)
ROOM_TELEPHONY_PIN_MAX_RETRIES = values.PositiveIntegerValue(
5,
environ_name="ROOM_TELEPHONY_PIN_MAX_RETRIES",
environ_prefix=None,
)
ROOM_TELEPHONY_PHONE_NUMBER = values.Value(
None,
environ_name="ROOM_TELEPHONY_PHONE_NUMBER",
environ_prefix=None,
)
ROOM_TELEPHONY_DEFAULT_COUNTRY = values.Value(
"US",
environ_name="ROOM_TELEPHONY_DEFAULT_COUNTRY",
environ_prefix=None,
)
# Roomkit (meeting-room SIP devices) integration
ROOMKIT_ENABLED = values.BooleanValue(
False,
environ_name="ROOMKIT_ENABLED",
environ_prefix=None,
)
# Server-to-server API token allowing the LiveKit SIP module to call the
# roomkit endpoints (e.g. join a room on behalf of a meeting-room device
# dialing in before any WebRTC participant).
ROOMKIT_SERVER_TO_SERVER_API_TOKEN = SecretFileValue(
None,
environ_name="ROOMKIT_SERVER_TO_SERVER_API_TOKEN",
environ_prefix=None,
)
# Subtitles settings
ROOM_SUBTITLE_ENABLED = values.BooleanValue(
False, environ_name="ROOM_SUBTITLE_ENABLED", environ_prefix=None
)
ROOM_SUBTITLE_AGENT_NAME = values.Value(
"multi-user-transcriber",
environ_name="ROOM_SUBTITLE_AGENT_NAME",
environ_prefix=None,
)
# Metadata collector settings
METADATA_COLLECTOR_ENABLED = values.BooleanValue(
False, environ_name="METADATA_COLLECTOR_ENABLED", environ_prefix=None
)
METADATA_COLLECTOR_AGENT_NAME = values.Value(
"metadata-collector",
environ_name="METADATA_COLLECTOR_AGENT_NAME",
environ_prefix=None,
)
METADATA_COLLECTOR_OUTPUT_FOLDER = values.Value(
"metadata",
environ_name="METADATA_COLLECTOR_OUTPUT_FOLDER",
environ_prefix=None,
)
# External Applications
APPLICATION_ENABLED = values.BooleanValue(
False, environ_name="APPLICATION_ENABLED", environ_prefix=None
)
APPLICATION_CLIENT_ID_LENGTH = values.PositiveIntegerValue(
40,
environ_name="APPLICATION_CLIENT_ID_LENGTH",
environ_prefix=None,
)
APPLICATION_CLIENT_SECRET_LENGTH = values.PositiveIntegerValue(
128,
environ_name="APPLICATION_CLIENT_SECRET_LENGTH",
environ_prefix=None,
)
APPLICATION_JWT_SECRET_KEY = SecretFileValue(
None, environ_name="APPLICATION_JWT_SECRET_KEY", environ_prefix=None
)
APPLICATION_JWT_ALG = values.Value(
"HS256",
environ_name="APPLICATION_JWT_ALG",
environ_prefix=None,
)
APPLICATION_JWT_ISSUER = values.Value(
"lasuite-meet",
environ_name="APPLICATION_JWT_ISSUER",
environ_prefix=None,
)
APPLICATION_JWT_AUDIENCE = values.Value(
None,
environ_name="APPLICATION_JWT_AUDIENCE",
environ_prefix=None,
)
APPLICATION_JWT_EXPIRATION_SECONDS = values.PositiveIntegerValue(
3600,
environ_name="APPLICATION_JWT_EXPIRATION_SECONDS",
environ_prefix=None,
)
APPLICATION_JWT_TOKEN_TYPE = values.Value(
"Bearer",
environ_name="APPLICATION_JWT_TOKEN_TYPE",
environ_prefix=None,
)
APPLICATION_BASE_URL = values.Value(
None,
environ_name="APPLICATION_BASE_URL",
environ_prefix=None,
)
# Warning: EXTERNAL_API_ALLOW_PUBLIC_ACCESS is ignored when
# EXTERNAL_API_DEFAULT_ACCESS_LEVEL=public.
EXTERNAL_API_ALLOW_PUBLIC_ACCESS = values.BooleanValue(
False,
environ_name="EXTERNAL_API_ALLOW_PUBLIC_ACCESS",
environ_prefix=None,
)
EXTERNAL_API_DEFAULT_ACCESS_LEVEL = values.Value(
"trusted",
environ_name="EXTERNAL_API_DEFAULT_ACCESS_LEVEL",
environ_prefix=None,
)
# Allows third-party platforms to create users with email-only identification.
# Required for external integrations, but fragile due to deferred user reconciliation
# on sub. Enable it with care /!\
APPLICATION_ALLOW_USER_CREATION = values.BooleanValue(
False,
environ_name="APPLICATION_ALLOW_USER_CREATION",
environ_prefix=None,
)
# Addons
ADDONS_ENABLED = values.BooleanValue(
False,
environ_name="ADDONS_ENABLED",
environ_prefix=None,
)
ADDONS_SESSION_TTL = values.PositiveIntegerValue(
3600,
environ_name="ADDONS_SESSION_TTL",
environ_prefix=None,
)
ADDONS_TRANSIT_TOKEN_TTL = values.PositiveIntegerValue(
120,
environ_name="ADDONS_TRANSIT_TOKEN_TTL",
environ_prefix=None,
)
ADDONS_CSRF_SECRET = SecretFileValue(
None,
environ_name="ADDONS_CSRF_SECRET",
environ_prefix=None,
)
ADDONS_CACHE_PREFIX_SESSION = values.Value(
"sid",
environ_name="ADDONS_CACHE_PREFIX_SESSION",
environ_prefix=None,
)
ADDONS_CACHE_PREFIX_TRANSIT = values.Value(
"transit",
environ_name="ADDONS_CACHE_PREFIX_TRANSIT",
environ_prefix=None,
)
ADDONS_TOKEN_AUDIENCE = values.Value(
"addons",
environ_name="ADDONS_TOKEN_AUDIENCE",
environ_prefix=None,
)
ADDONS_TOKEN_ISSUER = values.Value(
"lasuite-meet",
environ_name="ADDONS_TOKEN_ISSUER",
environ_prefix=None,
)
ADDONS_TOKEN_TTL = values.PositiveIntegerValue(
7200,
environ_name="ADDONS_TOKEN_TTL",
environ_prefix=None,
)
ADDONS_TOKEN_ALG = values.Value(
"HS256",
environ_name="ADDONS_TOKEN_ALG",
environ_prefix=None,
)
ADDONS_TOKEN_TYPE = values.Value(
"Bearer",
environ_name="ADDONS_TOKEN_TYPE",
environ_prefix=None,
)
ADDONS_TOKEN_SECRET_KEY = SecretFileValue(
None,
environ_name="ADDONS_TOKEN_SECRET_KEY",
environ_prefix=None,
)
ADDONS_TOKEN_SCOPE = values.Value(
"rooms:create",
environ_name="ADDONS_TOKEN_SCOPE",
environ_prefix=None,
)
ADDONS_RANDOM_TOKEN_BYTE_LENGTH = values.PositiveIntegerValue(
60,
environ_name="ADDONS_RANDOM_TOKEN_BYTE_LENGTH",
environ_prefix=None,
)
ADDONS_SESSION_ID_COOKIE = values.Value(
"addonsSid",
environ_name="ADDONS_SESSION_ID_COOKIE",
environ_prefix=None,
)
ADDONS_PENDING_SESSION_KEY = values.Value(
"addons_sid",
environ_name="ADDONS_PENDING_SESSION_KEY",
environ_prefix=None,
)
# Logging
# We want to make it easy to log to console but by default we log production
# to Sentry and don't want to log to console.
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {
"format": "{asctime} {name} {levelname} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "simple",
},
},
# Override root logger to send it to console
"root": {
"handlers": ["console"],
"level": values.Value(
"INFO", environ_name="LOGGING_LEVEL_LOGGERS_ROOT", environ_prefix=None
),
},
"loggers": {
"core": {
"handlers": ["console"],
"level": values.Value(
"INFO",
environ_name="LOGGING_LEVEL_LOGGERS_APP",
environ_prefix=None,
),
"propagate": False,
},
},
}
# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
"""Environment in which the application is launched."""
return self.__class__.__name__.lower()
# pylint: disable=invalid-name
@property
def RELEASE(self):
"""
Return the release information.
Delegate to the module function to enable easier testing.
"""
return get_release()
# pylint: disable=invalid-name
@property
def PARLER_LANGUAGES(self):
"""
Return languages for Parler computed from the LANGUAGES and LANGUAGE_CODE settings.
"""
return {
self.SITE_ID: tuple({"code": code} for code, _name in self.LANGUAGES),
"default": {
"fallbacks": [self.LANGUAGE_CODE],
"hide_untranslated": False,
},
}
@classmethod
def post_setup(cls):
"""Post setup configuration.
This is the place where you can configure settings that require other
settings to be loaded.
"""
super().post_setup()
if cls.FILE_UPLOAD_TMP_PATH == cls.FILE_UPLOAD_PATH:
raise ValueError(
"FILE_UPLOAD_TMP_PATH cannot be the same as FILE_UPLOAD_PATH"
)
if (
cls.SUMMARY_SERVICE_VERSION == 1
and cls.SUMMARY_SERVICE_ENDPOINT is not None
):
warnings.warn(
"SUMMARY_SERVICE_VERSION=1 is deprecated. "
"The legacy v1 API has been removed from the experimental "
"Summary service. Please update your Summary service deployment to "
"the v2 API and set SUMMARY_SERVICE_VERSION=2.",
# We use UserWarning to make sure it shows up in production deployment
UserWarning,
stacklevel=2,
)
# The SENTRY_DSN setting should be available to activate sentry for an environment
if cls.SENTRY_DSN is not None:
sentry_sdk.init(
dsn=cls.SENTRY_DSN,
environment=cls.__name__.lower(), # build, test, development, production
release=get_release(),
integrations=[DjangoIntegration()],
)
sentry_sdk.set_tag("application", "backend")
# Ignore the logs added by the DockerflowMiddleware
ignore_logger("request.summary")
class Build(Base):
"""Settings used when the application is built.
This environment should not be used to run the application. Just to build it with non-blocking
settings.
"""
SECRET_KEY = values.Value("DummyKey")
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": values.Value(
"whitenoise.storage.CompressedManifestStaticFilesStorage",
environ_name="STORAGES_STATICFILES_BACKEND",
),
},
}
class Development(Base):
"""
Development environment settings
We set DEBUG to True and configure the server to respond from all hosts.
"""
ALLOWED_HOSTS = ["*"]
CORS_ALLOW_ALL_ORIGINS = True
CSRF_TRUSTED_ORIGINS = ["http://localhost:8072", "http://localhost:3000"]
DEBUG = True
SESSION_COOKIE_NAME = "meet_sessionid"
USE_SWAGGER = True
def __init__(self):
# pylint: disable=invalid-name
self.INSTALLED_APPS += ["django_extensions", "drf_spectacular_sidecar"]
class Test(Base):
"""Test environment settings"""
LOGGING = values.DictValue(
{
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"meet": {
"handlers": ["console"],
"level": "DEBUG",
},
},
}
)
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
USE_SWAGGER = True
EXTERNAL_API_ENABLED = True
LIVEKIT_CONFIGURATION = {
"api_key": "devkey-padded-for-minimum-len!-livekit",
"api_secret": "secret-key-padded-for-minimum-len!-livekit",
"url": "http://127.0.0.1.nip.io:7880",
}
APPLICATION_ENABLED = True
APPLICATION_JWT_SECRET_KEY = "secret-key-padded-for-minimum-len!-application" # noqa:S105
APPLICATION_JWT_AUDIENCE = "Test inc."
CELERY_TASK_ALWAYS_EAGER = True
FILE_UPLOAD_ENABLED = True
ADDONS_ENABLED = True
ADDONS_CSRF_SECRET = "secret-key-padded-for-minimum-len!-addons" # noqa:S105
ADDONS_TOKEN_SECRET_KEY = "secret-key-padded-for-minimum-len!-addons" # noqa:S105
CONNECTION_TEST_ENABLED = True
def __init__(self):
# pylint: disable=invalid-name
self.INSTALLED_APPS += ["drf_spectacular_sidecar"]
class ContinuousIntegration(Test):
"""
Continuous Integration environment settings
nota bene: it should inherit from the Test environment.
"""
class Production(Base):
"""
Production environment settings
You must define the ALLOWED_HOSTS environment variable in Production
configuration (and derived configurations):
ALLOWED_HOSTS=["foo.com", "foo.fr"]
"""
# Security
ALLOWED_HOSTS = [
*values.ListValue([], environ_name="ALLOWED_HOSTS"),
gethostbyname(gethostname()),
]
CSRF_TRUSTED_ORIGINS = values.ListValue([])
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# SECURE_PROXY_SSL_HEADER allows to fix the scheme in Django's HttpRequest
# object when your application is behind a reverse proxy.
#
# Keep this SECURE_PROXY_SSL_HEADER configuration only if :
# - your Django app is behind a proxy.
# - your proxy strips the X-Forwarded-Proto header from all incoming requests
# - Your proxy sets the X-Forwarded-Proto header and sends it to Django
#
# In other cases, you should comment the following line to avoid security issues.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_HSTS_SECONDS = 60
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_SSL_REDIRECT = True
SECURE_REDIRECT_EXEMPT = [
"^__lbheartbeat__",
"^__heartbeat__",
]
# Modern browsers require to have the `secure` attribute on cookies with `Samesite=none`
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
# Privacy
SECURE_REFERRER_POLICY = "same-origin"
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": values.Value(
"redis://redis:6379/1",
environ_name="REDIS_URL",
environ_prefix=None,
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
class Feature(Production):
"""
Feature environment settings
nota bene: it should inherit from the Production environment.
"""
class Staging(Production):
"""
Staging environment settings
nota bene: it should inherit from the Production environment.
"""
class Sandbox(Production):
"""
Sandbox environment settings
nota bene: it should inherit from the Production environment.
"""
class PreProduction(Production):
"""
Pre-production environment settings
nota bene: it should inherit from the Production environment.
"""
class Demo(Production):
"""
Demonstration environment settings
nota bene: it should inherit from the Production environment.
"""
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}