Files
lasuite-drive/src/backend/drive/settings.py
T
Manuel Raynaud 46d61a9c76 🐛(backend) correctly configure celery beat to run wopi configuration
The celery beat configuration was not working correctly, we installed
the database scheduler but we didn't configure it correctly. We prefer
removing this scheduler to use the default one. Instead, the crontab
part can be configured using environment variable. By default it will be
run every night at 3AM.
2026-02-02 08:05:18 +01:00

1696 lines
52 KiB
Python
Executable File

"""
Django settings for drive 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/
"""
import os
import tomllib
from socket import gethostbyname, gethostname
from django.utils.translation import gettext_lazy as _
import dj_database_url
import posthog
import sentry_sdk
from boto3.s3.transfer import TransferConfig
from configurations import Configuration, values
from lasuite.configuration.values import SecretFileValue
from sentry_sdk.integrations.django import DjangoIntegration
# pylint: disable=too-many-lines
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.environ.get("DATA_DIR", os.path.join("/", "data"))
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
def get_release():
"""
Get the current release of the application
"""
try:
with open(os.path.join(BASE_DIR, "pyproject.toml"), "rb") as f:
pyproject_data = tomllib.load(f)
return pyproject_data["project"]["version"]
except (FileNotFoundError, KeyError):
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:
* SENTRY_DSN
* DB_NAME
* DB_HOST
* DB_PASSWORD
* DB_USER
"""
DEBUG = False
LOAD_E2E_URLS = False
USE_SWAGGER = False
API_VERSION = "v1.0"
# Security
ALLOWED_HOSTS = values.ListValue([])
SECRET_KEY = SecretFileValue(None)
SERVER_TO_SERVER_API_TOKENS = values.ListValue([])
# Application definition
ROOT_URLCONF = "drive.urls"
WSGI_APPLICATION = "drive.wsgi.application"
# Database
DATABASES = {
"default": dj_database_url.config()
if os.environ.get("DATABASE_URL")
else {
"ENGINE": values.Value(
"django.db.backends.postgresql",
environ_name="DB_ENGINE",
environ_prefix=None,
),
"NAME": values.Value("drive", 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"
# Search
SEARCH_INDEXER_CLASS = values.Value(
default=None,
environ_name="SEARCH_INDEXER_CLASS",
environ_prefix=None,
)
SEARCH_INDEXER_BATCH_SIZE = values.IntegerValue(
default=100_000, environ_name="SEARCH_INDEXER_BATCH_SIZE", environ_prefix=None
)
SEARCH_INDEXER_URL = values.Value(
default=None, environ_name="SEARCH_INDEXER_URL", environ_prefix=None
)
SEARCH_INDEXER_COUNTDOWN = values.IntegerValue(
default=1, environ_name="SEARCH_INDEXER_COUNTDOWN", environ_prefix=None
)
SEARCH_INDEXER_SECRET = values.Value(
default=None, environ_name="SEARCH_INDEXER_SECRET", environ_prefix=None
)
SEARCH_INDEXER_QUERY_URL = values.Value(
default=None, environ_name="SEARCH_INDEXER_QUERY_URL", environ_prefix=None
)
SEARCH_INDEXER_QUERY_LIMIT = values.PositiveIntegerValue(
default=50, environ_name="SEARCH_INDEXER_QUERY_LIMIT", environ_prefix=None
)
SEARCH_INDEXER_CONTENT_MAX_SIZE = values.PositiveIntegerValue(
2 * MB,
environ_name="SEARCH_INDEXER_CONTENT_MAX_SIZE",
environ_prefix=None,
)
SEARCH_INDEXER_ALLOWED_MIMETYPES = values.ListValue(
["text/"],
environ_name="SEARCH_INDEXER_ALLOWED_MIMETYPES",
environ_prefix=None,
)
# Static files (CSS, JavaScript, Images)
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(DATA_DIR, "static")
MEDIA_URL = "/media/"
MEDIA_URL_PREVIEW = "/media/preview/"
MEDIA_ROOT = os.path.join(DATA_DIR, "media")
MEDIA_BASE_URL = values.Value(
None, environ_name="MEDIA_BASE_URL", environ_prefix=None
)
SITE_ID = 1
STORAGES = {
"default": {
"BACKEND": "storages.backends.s3.S3Storage",
"OPTIONS": {
"transfer_config": TransferConfig(
use_threads=values.BooleanValue(
default=True,
environ_name="S3_TRANSFER_CONFIG_USE_THREADS",
environ_prefix=None,
),
multipart_threshold=values.PositiveIntegerValue(
default=8 * MB,
environ_name="S3_TRANSFER_CONFIG_MULTIPART_THRESHOLD",
environ_prefix=None,
),
multipart_chunksize=values.PositiveIntegerValue(
default=8 * MB,
environ_name="S3_TRANSFER_CONFIG_MULTIPART_CHUNKSIZE",
environ_prefix=None,
),
max_concurrency=values.PositiveIntegerValue(
default=10,
environ_name="S3_TRANSFER_CONFIG_MAX_CONCURRENCY",
environ_prefix=None,
),
),
},
},
"staticfiles": {
"BACKEND": values.Value(
"whitenoise.storage.CompressedManifestStaticFilesStorage",
environ_name="STORAGES_STATICFILES_BACKEND",
),
},
}
FEATURES_INDEXED_SEARCH = values.BooleanValue(
default=True, environ_name="FEATURES_INDEXED_SEARCH", environ_prefix=None
)
# Posthog
POSTHOG_KEY = SecretFileValue(None, environ_name="POSTHOG_KEY", environ_prefix=None)
POSTHOG_HOST = values.Value(
"https://eu.i.posthog.com", environ_name="POSTHOG_HOST", environ_prefix=None
)
# 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(
"drive-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,
)
# Maximum size of the request body in memory.
# This is used to limit the size of the request body in memory.
# This also limits the size of the file that can be uploaded to the server.
DATA_UPLOAD_MAX_MEMORY_SIZE = values.PositiveIntegerValue(
2 * GB,
environ_name="DATA_UPLOAD_MAX_MEMORY_SIZE",
environ_prefix=None,
)
RESTRICT_UPLOAD_FILE_TYPE = values.BooleanValue(
default=True,
environ_name="RESTRICT_UPLOAD_FILE_TYPE",
environ_prefix=None,
)
FILE_EXTENSIONS_ALLOWED = values.ListValue(
[
".001",
".002",
".003",
".3dm",
".3dmf",
".3g2",
".3gp",
".7z",
".7z.001",
".FCStd",
".LDR",
".N",
".SLDDRW",
".SLDPRT",
".STEP",
".aac",
".accdb",
".acidcsa",
".acidppr",
".acidsca",
".acidssa",
".afr",
".ai",
".aif",
".alx",
".apafis",
".ape",
".apk",
".arc",
".asc",
".ase",
".asy",
".au",
".aux",
".avi",
".axx",
".bed",
".bib",
".bin",
".bmp",
".bpm",
".bpmn",
".bz",
".bz2",
".c",
".cdc",
".cdxml",
".cer",
".cif",
".clmc",
".clmx",
".cpg",
".crt",
".crypto",
".csr",
".csv",
".dat",
".dbf",
".deb",
".dgn",
".direct",
".djvu",
".dna",
".doc",
".docm",
".docx",
".docxf",
".dot",
".dotm",
".dotx",
".drawio",
".drodm",
".dsf",
".dsn",
".dwg",
".dwt",
".dxf",
".dxi",
".ecw",
".eda",
".edi",
".egg",
".emf",
".eml",
".ems",
".enlx",
".eot",
".epgz",
".epoc",
".eps",
".epub",
".err",
".excalidraw",
".ext1",
".ext2",
".ext3",
".f0",
".f3d",
".fas",
".fasta",
".fcstd",
".fic",
".fke",
".fodg",
".fodp",
".fods",
".fodt",
".freeplane",
".gan",
".gbk",
".gcode",
".geo",
".geojson",
".gif",
".gml",
".gpkg",
".gpx",
".gradle",
".graffle",
".graphml",
".grist",
".gz",
".heic",
".ico",
".ics",
".id",
".ifc",
".igs",
".ind",
".indd",
".int",
".ipynb",
".jif",
".jp2",
".jpe",
".jpeg",
".jpg",
".json",
".kdbx",
".key",
".kml",
".kmz",
".layout",
".loc",
".log",
".lss",
".lst",
".m",
".m4a",
".m4b",
".m4p",
".m4r",
".m4v",
".map",
".mbz",
".mcd",
".md",
".mdb",
".mkv",
".mm",
".moo",
".mov",
".mp3",
".mp4",
".mpd",
".mpeg",
".mpga",
".mph",
".mpp",
".mri",
".mrk",
".msg",
".mtgl",
".mts",
".mtz",
".mvdx",
".naf",
".nist",
".nitrace",
".numbers",
".oaf",
".obj",
".odb",
".odc",
".odf",
".odg",
".odi",
".odm",
".odp",
".ods",
".odt",
".oform",
".oft",
".one",
".opj",
".opju",
".osf",
".otc",
".otf",
".otg",
".oth",
".oti",
".oto",
".otp",
".ots",
".ott",
".p7s",
".pages",
".pbix",
".pdb",
".pdf",
".pdfxml",
".pgw",
".ply",
".png",
".pot",
".potm",
".potx",
".pps",
".ppsm",
".ppsx",
".ppt",
".pptm",
".pptx",
".pr1",
".pr2",
".prism",
".prj",
".properties",
".proto",
".prproj",
".psd",
".pub",
".puw",
".py",
".pyc",
".pzfx",
".qgr",
".qgs",
".qgz",
".qix",
".qmd",
".qml",
".qpj",
".qpt",
".r",
".rar",
".rdata",
".rds",
".rep",
".ris",
".rmd",
".rtf",
".rvt",
".sb2",
".sb3",
".sbn",
".sbx",
".scwsp",
".sh",
".shp",
".shx",
".sig",
".skp",
".sldasm",
".slddrw",
".slk",
".sphd",
".sphx",
".sql",
".srt",
".ssp",
".stc",
".std",
".stl",
".stp",
".stw",
".svg",
".swu",
".sxc",
".sxd",
".sxg",
".sxi",
".sxm",
".sxw",
".tab",
".tar",
".tex",
".text",
".textgrid",
".tfw",
".tif",
".tiff",
".tsv",
".ttc",
".ttf",
".twbx",
".txt",
".vcf",
".vsd",
".vsdx",
".vtt",
".vuw",
".wa1",
".wav",
".weba",
".webm",
".webp",
".wfl",
".woff",
".woff2",
".wsp",
".xcf",
".xd",
".xhl",
".xls",
".xlsb",
".xlsm",
".xlsx",
".xlt",
".xltm",
".xltx",
".xmind",
".xml",
".xps",
".xsd",
".xz",
".yml",
".zed",
".zip",
],
environ_name="FILE_EXTENSIONS_ALLOWED",
environ_prefix=None,
)
FILE_MIMETYPE_ALLOWED = values.ListValue(
[
"application/epub+zip",
"application/gml+xml",
"application/gpx+xml",
"application/gzip",
"application/json",
"application/msword",
"application/pdf",
"application/pgp-signature",
"application/pkcs10",
"application/pkcs7-signature",
"application/pkix-cert",
"application/postscript",
"application/rtf",
"application/sql",
"application/vnd.android.package-archive",
"application/vnd.businessobjects",
"application/vnd.chemdraw+xml",
"application/vnd.debian.binary-package",
"application/vnd.dna",
"application/vnd.dynageo",
"application/vnd.google-earth.kml+xml",
"application/vnd.google-earth.kmz",
"application/vnd.koan",
"application/vnd.mcd",
"application/vnd.ms-excel",
"application/vnd.ms-excel.sheet.binary.macroenabled.12",
"application/vnd.ms-excel.sheet.macroenabled.12",
"application/vnd.ms-excel.template.macroenabled.12",
"application/vnd.ms-fontobject",
"application/vnd.ms-outlook",
"application/vnd.ms-pki.stl",
"application/vnd.ms-powerpoint",
"application/vnd.ms-powerpoint.presentation.macroenabled.12",
"application/vnd.ms-powerpoint.slideshow.macroenabled.12",
"application/vnd.ms-powerpoint.template.macroenabled.12",
"application/vnd.ms-project",
"application/vnd.ms-word.document.macroenabled.12",
"application/vnd.ms-word.template.macroenabled.12",
"application/vnd.ms-xpsdocument",
"application/vnd.oasis.opendocument.chart",
"application/vnd.oasis.opendocument.chart-template",
"application/vnd.oasis.opendocument.database",
"application/vnd.oasis.opendocument.formula",
"application/vnd.oasis.opendocument.graphics",
"application/vnd.oasis.opendocument.graphics-template",
"application/vnd.oasis.opendocument.image",
"application/vnd.oasis.opendocument.image-template",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.presentation-template",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.spreadsheet-template",
"application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.text-master",
"application/vnd.oasis.opendocument.text-template",
"application/vnd.oasis.opendocument.text-web",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.palm",
"application/vnd.realvnc.bed",
"application/vnd.sun.xml.calc",
"application/vnd.sun.xml.calc.template",
"application/vnd.sun.xml.draw",
"application/vnd.sun.xml.draw.template",
"application/vnd.sun.xml.impress",
"application/vnd.sun.xml.math",
"application/vnd.sun.xml.writer",
"application/vnd.sun.xml.writer.global",
"application/vnd.sun.xml.writer.template",
"application/vnd.visio",
"application/vnd.yamaha.openscoreformat",
"application/x-7z-compressed",
"application/x-bzip",
"application/x-bzip2",
"application/x-debian-package",
"application/x-empty",
"application/x-freearc",
"application/x-keepass",
"application/x-keepass2",
"application/x-msaccess",
"application/x-msmetafile",
"application/x-mspublisher",
"application/x-python-bytecodeapplication/x-shellscript",
"application/x-python-code",
"application/x-rar",
"application/x-rar-compressed",
"application/x-research-info-systems",
"application/x-sh",
"application/x-sql",
"application/x-subrip",
"application/x-tar",
"application/x-tex",
"application/x-tgif",
"application/x-x509-ca-cert",
"application/x-xz",
"application/xml",
"application/yaml",
"application/zip",
"audio/aac",
"audio/basic",
"audio/mp4",
"audio/mpeg",
"audio/wav",
"audio/webm",
"audio/x-aac",
"audio/x-aiff",
"audio/x-wav",
"chemical/x-cif",
"font/collection",
"font/otf",
"font/ttf",
"font/woff",
"font/woff2",
"image/bmp",
"image/gif",
"image/heic",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/tiff",
"image/vnd.adobe.photoshop",
"image/vnd.djvu",
"image/vnd.dwg",
"image/vnd.dxf",
"image/webp",
"image/x-icon",
"message/rfc822",
"model/iges",
"text/calendar",
"text/csv",
"text/markdown",
"text/plain",
"text/tab-separated-values",
"text/vtt",
"text/x-c",
"text/x-python",
"text/x-vcard",
"video/3gpp",
"video/3gpp2",
"video/mp2t",
"video/mp4",
"video/mpeg",
"video/quicktime",
"video/webm",
"video/x-m4v",
"video/x-matroska",
"video/x-msvideo",
# Fallback
"application/octet-stream",
],
environ_name="FILE_MIMETYPE_ALLOWED",
environt_prefix=None,
)
ITEM_PREVIEWABLE_MIME_TYPES = values.ListValue(
[
"image/",
"video/",
"audio/",
"application/pdf",
],
environ_name="ITEM_PREVIEWABLE_MIME_TYPES",
environ_prefix=None,
)
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
# Languages
LANGUAGE_CODE = values.Value("en-us")
LANGUAGE_COOKIE_NAME = "drive_language" # cookie & language is set from frontend
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")),
("de-de", _("German")),
("nl-nl", _("Dutch")),
)
)
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),)
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Templates
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.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 = [
"core",
"wopi",
"drf_spectacular",
"drf_standardized_errors",
# Third party apps
"corsheaders",
"django_filters",
"dockerflow.django",
"rest_framework",
"rest_framework_api_key",
"parler",
"django_ltree",
"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",
"lasuite.malware_detection",
]
# Cache
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": values.Value(
"redis://redis:6379/0",
environ_name="REDIS_URL",
environ_prefix=None,
),
"TIMEOUT": values.IntegerValue(
30, # timeout in seconds
environ_name="CACHES_DEFAULT_TIMEOUT",
environ_prefix=None,
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
"KEY_PREFIX": values.Value(
"drive",
environ_name="CACHES_DEFAULT_KEY_PREFIX",
environ_prefix=None,
),
},
"session": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": values.Value(
"redis://redis:6379/0",
environ_name="REDIS_URL",
environ_prefix=None,
),
"TIMEOUT": values.IntegerValue(
30, # timeout in seconds
environ_name="CACHES_SESSION_TIMEOUT",
environ_prefix=None,
),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"mozilla_django_oidc.contrib.drf.OIDCAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
"nested_multipart_parser.drf.DrfNestedParser",
],
"DEFAULT_RENDERER_CLASSES": [
# 🔒️ Disable BrowsableAPIRenderer which provides forms allowing a user to
# see all the data in the database (ie a serializer with a ForeignKey field
# will generate a form with a field with all possible values of the FK).
"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_CLASSES": ["rest_framework.throttling.ScopedRateThrottle"],
"DEFAULT_THROTTLE_RATES": {
"user_list_sustained": values.Value(
default="180/hour",
environ_name="API_USERS_LIST_THROTTLE_RATE_SUSTAINED",
environ_prefix=None,
),
"user_list_burst": values.Value(
default="30/minute",
environ_name="API_USERS_LIST_THROTTLE_RATE_BURST",
environ_prefix=None,
),
"sdk_event_relay": values.Value(
default="200/minute",
environ_name="API_SDK_EVENT_RELAY_THROTTLE_RATE",
environ_prefix=None,
),
},
}
MAX_PAGE_SIZE = values.PositiveIntegerValue(
200, environ_name="MAX_PAGE_SIZE", environ_prefix=None
)
SPECTACULAR_SETTINGS = {
"TITLE": "drive API",
"DESCRIPTION": "This is the drive 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",
}
TRASHBIN_CUTOFF_DAYS = values.Value(
30, environ_name="TRASHBIN_CUTOFF_DAYS", environ_prefix=None
)
# Mail
EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend")
EMAIL_BRAND_NAME = values.Value(None)
EMAIL_HOST = values.Value(None)
EMAIL_HOST_USER = values.Value(None)
EMAIL_HOST_PASSWORD = SecretFileValue(None)
EMAIL_LOGO_IMG = values.Value(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")
AUTH_USER_MODEL = "core.User"
INVITATION_VALIDITY_DURATION = 604800 # 7 days, in seconds
# 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", environ_prefix=None)
# Frontend
FRONTEND_THEME = values.Value(
None, environ_name="FRONTEND_THEME", environ_prefix=None
)
FRONTEND_MORE_LINK = values.Value(
None,
environ_name="FRONTEND_MORE_LINK",
environ_prefix=None,
)
FRONTEND_FEEDBACK_BUTTON_SHOW = values.BooleanValue(
default=False, environ_name="FRONTEND_FEEDBACK_BUTTON_SHOW", environ_prefix=None
)
# For instance, you might want to bind this button to an external library like
# Posthog to trigger survey instead of the build in feedback modal.
FRONTEND_FEEDBACK_BUTTON_IDLE = values.BooleanValue(
default=False, environ_name="FRONTEND_FEEDBACK_BUTTON_IDLE", environ_prefix=None
)
FRONTEND_FEEDBACK_ITEMS = values.DictValue(
{}, environ_name="FRONTEND_FEEDBACK_ITEMS", environ_prefix=None
)
FRONTEND_FEEDBACK_MESSAGES_WIDGET_ENABLED = values.BooleanValue(
default=False,
environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_ENABLED",
environ_prefix=None,
)
FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL = values.Value(
None,
environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL",
environ_prefix=None,
)
FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL = values.Value(
None,
environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL",
environ_prefix=None,
)
FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH = values.Value(
None, environ_name="FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH", environ_prefix=None
)
FRONTEND_HIDE_GAUFRE = values.BooleanValue(
default=False, environ_name="FRONTEND_HIDE_GAUFRE", environ_prefix=None
)
THEME_CUSTOMIZATION_FILE_PATH = values.Value(
os.path.join(BASE_DIR, "drive/configuration/theme/default.json"),
environ_name="THEME_CUSTOMIZATION_FILE_PATH",
environ_prefix=None,
)
THEME_CUSTOMIZATION_CACHE_TIMEOUT = values.Value(
60 * 60 * 24,
environ_name="THEME_CUSTOMIZATION_CACHE_TIMEOUT",
environ_prefix=None,
)
# Crisp
CRISP_WEBSITE_ID = values.Value(
None, environ_name="CRISP_WEBSITE_ID", environ_prefix=None
)
# Easy thumbnails
THUMBNAIL_EXTENSION = "webp"
THUMBNAIL_TRANSPARENCY_EXTENSION = "webp"
THUMBNAIL_DEFAULT_STORAGE_ALIAS = "default"
THUMBNAIL_ALIASES = {}
# Celery
CELERY_BROKER_URL = values.Value("redis://redis:6379/0")
CELERY_BROKER_TRANSPORT_OPTIONS = values.DictValue({})
# Session
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
SESSION_COOKIE_AGE = 60 * 60 * 12
# OIDC - Authorization Code Flow
OIDC_CREATE_USER = values.BooleanValue(
default=True,
environ_name="OIDC_CREATE_USER",
)
OIDC_CALLBACK_CLASS = "core.authentication.views.OIDCAuthenticationCallbackView"
OIDC_RP_SIGN_ALGO = values.Value(
"RS256", environ_name="OIDC_RP_SIGN_ALGO", environ_prefix=None
)
OIDC_RP_CLIENT_ID = values.Value(
"drive", 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_LOGOUT_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_LOGOUT_ENDPOINT", environ_prefix=None
)
OIDC_REDIRECT_FIELD_NAME = values.Value(
"returnTo", environ_name="OIDC_REDIRECT_FIELD_NAME", 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
)
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
)
OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION = values.BooleanValue(
default=True,
environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION",
environ_prefix=None,
)
OIDC_STORE_ACCESS_TOKEN = values.BooleanValue(
default=False, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None
)
OIDC_STORE_REFRESH_TOKEN = values.BooleanValue(
default=False, environ_name="OIDC_STORE_REFRESH_TOKEN", environ_prefix=None
)
OIDC_STORE_REFRESH_TOKEN_KEY = values.Value(
default=None,
environ_name="OIDC_STORE_REFRESH_TOKEN_KEY",
environ_prefix=None,
)
# OIDC claims to store
OIDC_STORE_CLAIMS = values.ListValue(
default=[],
environ_name="OIDC_STORE_CLAIMS",
environ_prefix=None,
)
# WARNING: Enabling this setting allows multiple user accounts to share the same email
# address. This may cause security issues and is not recommended for production use when
# email is activated as fallback for identification (see previous setting).
OIDC_ALLOW_DUPLICATE_EMAILS = values.BooleanValue(
default=False,
environ_name="OIDC_ALLOW_DUPLICATE_EMAILS",
environ_prefix=None,
)
OIDC_USER_INFO = values.ListValue(
default=values.ListValue( # retrocompatibility
default=[],
environ_name="USER_OIDC_ESSENTIAL_CLAIMS",
environ_prefix=None,
),
environ_name="OIDC_USER_INFO",
environ_prefix=None,
)
OIDC_USERINFO_FULLNAME_FIELDS = values.ListValue(
default=["first_name", "last_name"],
environ_name="OIDC_USERINFO_FULLNAME_FIELDS",
environ_prefix=None,
)
OIDC_USERINFO_SHORTNAME_FIELD = values.Value(
default="first_name",
environ_name="OIDC_USERINFO_SHORTNAME_FIELD",
environ_prefix=None,
)
# OIDC Resource Server
OIDC_RESOURCE_SERVER_ENABLED = values.BooleanValue(
default=False, environ_name="OIDC_RESOURCE_SERVER_ENABLED", environ_prefix=None
)
OIDC_RS_BACKEND_CLASS = values.Value(
"lasuite.oidc_resource_server.backend.ResourceServerBackend",
environ_name="OIDC_RS_BACKEND_CLASS",
environ_prefix=None,
)
OIDC_OP_URL = values.Value(None, environ_name="OIDC_OP_URL", environ_prefix=None)
OIDC_VERIFY_SSL = values.BooleanValue(
default=True, environ_name="OIDC_VERIFY_SSL", environ_prefix=None
)
OIDC_TIMEOUT = values.PositiveIntegerValue(
3, environ_name="OIDC_TIMEOUT", environ_prefix=None
)
OIDC_PROXY = values.Value(None, environ_name="OIDC_PROXY", environ_prefix=None)
OIDC_OP_INTROSPECTION_ENDPOINT = values.Value(
None, environ_name="OIDC_OP_INTROSPECTION_ENDPOINT", environ_prefix=None
)
OIDC_RS_CLIENT_ID = values.Value(
None, environ_name="OIDC_RS_CLIENT_ID", environ_prefix=None
)
OIDC_RS_CLIENT_SECRET = values.Value(
None, environ_name="OIDC_RS_CLIENT_SECRET", environ_prefix=None
)
OIDC_RS_AUDIENCE_CLAIM = values.Value(
"client_id", environ_name="OIDC_RS_AUDIENCE_CLAIM", environ_prefix=None
)
OIDC_RS_ENCRYPTION_ENCODING = values.Value(
"A256GCM", environ_name="OIDC_RS_ENCRYPTION_ENCODING", environ_prefix=None
)
OIDC_RS_ENCRYPTION_ALGO = values.Value(
"RSA-OAEP", environ_name="OIDC_RS_ENCRYPTION_ALGO", environ_prefix=None
)
OIDC_RS_SIGNING_ALGO = values.Value(
"ES256", environ_name="OIDC_RS_SIGNING_ALGO", environ_prefix=None
)
OIDC_RS_SCOPES = values.ListValue(
["openid"], environ_name="OIDC_RS_SCOPES", environ_prefix=None
)
OIDC_RS_ALLOWED_AUDIENCES = values.ListValue(
default=[],
environ_name="OIDC_RS_ALLOWED_AUDIENCES",
environ_prefix=None,
)
# External API Configuration
# Configure available routes and actions for external_api endpoints
EXTERNAL_API = values.DictValue(
default={
"items": {
"enabled": True,
"actions": ["list", "retrieve", "children", "upload_ended"],
},
"item_access": {
"enabled": False,
"actions": [],
},
"item_invitation": {
"enabled": False,
"actions": [],
},
"users": {
"enabled": True,
"actions": ["get_me"],
},
},
environ_name="EXTERNAL_API",
environ_prefix=None,
)
OIDC_RS_PRIVATE_KEY_STR = values.Value(
default=None,
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,
)
# SDK Relay
SDK_RELAY_CACHE_TIMEOUT = values.PositiveIntegerValue(
600, # 10 minutes
environ_name="SDK_RELAY_CACHE_TIMEOUT",
environ_prefix=None,
)
SDK_CORS_ALLOWED_ORIGINS = values.ListValue(
default=[],
environ_name="SDK_CORS_ALLOWED_ORIGINS",
environ_prefix=None,
)
ALLOW_LOGOUT_GET_METHOD = values.BooleanValue(
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", 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": True,
},
},
}
API_USERS_LIST_LIMIT = values.PositiveIntegerValue(
default=5,
environ_name="API_USERS_LIST_LIMIT",
environ_prefix=None,
)
# WOPI
# WOPI_CLIENTS contains a list of client name. These client names will be used in the post_setup
# to configure the settings related to the client.
# Example :
# WOPI_CLIENTS = ["vendorA"]
# Then these settings will be cheked in the post_setup:
# WOPI_VENDORA_DISCOVERY_URL = https://vendorA.com/hosting/discovery
# If they are missing, a ValueError will be raised.
WOPI_CLIENTS = values.ListValue(
[], environ_name="WOPI_CLIENTS", environ_prefix=None
)
WOPI_CLIENTS_CONFIGURATION = {}
WOPI_EXCLUDED_MIMETYPES = values.ListValue(
[
"image/svg+xml",
"image/png",
"image/jpg",
"image/jpeg",
"image/gif",
"image/bmp",
"image/tiff",
"image/webp",
"application/pdf",
"image/x-wpg",
"image/cgm",
"image/vnd.dxf",
"image/x-emf",
"image/x-wmf",
"image/x-freehand",
],
environ_name="WOPI_EXCLUDED_MIMETYPES",
environ_prefix=None,
)
WOPI_EXCLUDED_EXTENSIONS = values.ListValue(
["svg", "png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp", "pdf"],
environ_name="WOPI_EXCLUDED_EXTENSIONS",
environ_prefix=None,
)
WOPI_CONFIGURATION_CACHE_EXPIRATION = values.IntegerValue(
60 * 60 * 24,
environ_name="WOPI_CONFIGURATION_CACHE_EXPIRATION",
environ_prefix=None,
)
WOPI_SRC_BASE_URL = values.Value(
None,
environ_name="WOPI_SRC_BASE_URL",
environ_prefix=None,
)
# We recommend using an access_token_ttl value that makes the access token valid for 10 hours.
WOPI_ACCESS_TOKEN_TIMEOUT = values.IntegerValue(
60 * 60 * 10, environ_name="WOPI_ACCESS_TOKEN_TIMEOUT", environ_prefix=None
)
WOPI_LOCK_TIMEOUT = values.IntegerValue(
30 * 60, environ_name="WOPI_LOCK_TIMEOUT", environ_prefix=None
)
WOPI_DISABLE_CHAT = values.IntegerValue(
0, environ_name="WOPI_DISABLE_CHAT", environ_prefix=None
)
WOPI_CONFIGURATION_CRONTAB_MINUTE = values.Value(
0, environ_name="WOPI_CONFIGURATION_CRONTAB_MINUTE", environ_prefix=None
)
WOPI_CONFIGURATION_CRONTAB_HOUR = values.Value(
3, environ_name="WOPI_CONFIGURATION_CRONTAB_HOUR", environ_prefix=None
)
WOPI_CONFIGURATION_CRONTAB_DAY_OF_MONTH = values.Value(
"*", environ_name="WOPI_CONFIGURATION_CRONTAB_DAY_OF_MONTH", environ_prefix=None
)
WOPI_CONFIGURATION_CRONTAB_MONTH_OF_YEAR = values.Value(
"*",
environ_name="WOPI_CONFIGURATION_CRONTAB_MONTH_OF_YEAR",
environ_prefix=None,
)
# Malware detection
MALWARE_DETECTION = {
"BACKEND": values.Value(
"lasuite.malware_detection.backends.dummy.DummyBackend",
environ_name="MALWARE_DETECTION_BACKEND",
environ_prefix=None,
),
"PARAMETERS": values.DictValue(
default={
"callback_path": "core.malware_detection.malware_detection_callback",
},
environ_name="MALWARE_DETECTION_PARAMETERS",
environ_prefix=None,
),
}
# Metrics
METRICS_ENABLED = values.BooleanValue(
False,
environ_name="METRICS_ENABLED",
environ_prefix=None,
)
METRICS_USER_CLAIMS_EXPOSED = values.ListValue(
[],
environ_name="METRICS_USER_CLAIMS_EXPOSED",
environ_prefix=None,
)
# Storage compute
STORAGE_COMPUTE_BACKEND = values.Value(
"core.storage.creator_storage_compute_backend.CreatorStorageComputeBackend",
environ_name="STORAGE_COMPUTE_BACKEND",
environ_prefix=None,
)
# Entitlements
ENTITLEMENTS_BACKEND = values.Value(
"core.entitlements.dummy_entitlements_backend.DummyEntitlementsBackend",
environ_name="ENTITLEMENTS_BACKEND",
environ_prefix=None,
)
ENTITLEMENTS_BACKEND_PARAMETERS = values.DictValue(
{},
environ_name="ENTITLEMENTS_BACKEND_PARAMETERS",
environ_prefix=None,
)
# 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()
# 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(),
release=get_release(),
integrations=[DjangoIntegration()],
)
sentry_sdk.set_tag("application", "backend")
if (
cls.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION
and cls.OIDC_ALLOW_DUPLICATE_EMAILS
):
raise ValueError(
"Both OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION and "
"OIDC_ALLOW_DUPLICATE_EMAILS cannot be set to True simultaneously. "
)
if cls.POSTHOG_KEY is not None:
posthog.api_key = cls.POSTHOG_KEY
posthog.host = cls.POSTHOG_HOST
if cls.WOPI_CLIENTS:
for wopi_client in cls.WOPI_CLIENTS:
wopi_client_upper = wopi_client.upper()
cls.WOPI_CLIENTS_CONFIGURATION[wopi_client] = {
"discovery_url": values.Value(
None,
environ_name=f"WOPI_{wopi_client_upper}_DISCOVERY_URL",
environ_prefix=None,
environ_required=True,
),
"mimetypes": {},
"extensions": {},
}
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.
"""
SESSION_CACHE_ALIAS = "default"
CACHES = {
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
}
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
LOAD_E2E_URLS = True
SESSION_COOKIE_NAME = "drive_sessionid"
USE_SWAGGER = True
DEBUG_TOOLBAR_CONFIG = {
"SHOW_TOOLBAR_CALLBACK": lambda request: True,
}
def __init__(self):
# pylint: disable=invalid-name
self.MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
# pylint: disable=invalid-name
self.INSTALLED_APPS += [
"django_extensions",
"drf_spectacular_sidecar",
"debug_toolbar",
"e2e",
"demo",
]
class Test(Base):
"""Test environment settings"""
SESSION_CACHE_ALIAS = "default"
CACHES = {
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
}
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
USE_SWAGGER = True
CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(True)
FEATURES_INDEXED_SEARCH = True
SEARCH_INDEXER_CLASS = None
OIDC_STORE_ACCESS_TOKEN = False
OIDC_STORE_REFRESH_TOKEN = False
def __init__(self):
# pylint: disable=invalid-name
self.INSTALLED_APPS += ["drf_spectacular_sidecar", "e2e", "demo"]
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
# Add allowed host from environment variables.
# The machine hostname is added by default,
# it makes the application pingable by a load balancer on the same machine by example
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_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"
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 PreProduction(Production):
"""
Pre-production environment settings
nota bene: it should inherit from the Production environment.
"""