397 lines
13 KiB
Python
397 lines
13 KiB
Python
"""
|
|
Django settings for blenderid project.
|
|
|
|
Generated by 'django-admin startproject' using Django 1.9.10.
|
|
|
|
For more information on this file, see
|
|
https://docs.djangoproject.com/en/1.9/topics/settings/
|
|
|
|
For the full list of settings and their values, see
|
|
https://docs.djangoproject.com/en/1.9/ref/settings/
|
|
"""
|
|
|
|
import datetime
|
|
import os
|
|
import pathlib
|
|
import pytz
|
|
import sys
|
|
|
|
from dotenv import load_dotenv
|
|
from django.urls import reverse_lazy
|
|
import dj_database_url
|
|
|
|
|
|
# Load variables from .env, if available
|
|
path = os.path.dirname(os.path.abspath(__file__)) + '/../.env'
|
|
if os.path.isfile(path):
|
|
load_dotenv(path)
|
|
|
|
BASE_DIR = pathlib.Path(__file__).absolute().parent.parent
|
|
|
|
# Used when generating links to ourselves outside of the context of a request.
|
|
# Preferrably HttpRequest.build_absolute_uri() is used.
|
|
PREFERRED_SCHEME = "https"
|
|
|
|
# SECURITY WARNING: keep the secret key used in production secret!
|
|
# Update this to something unique for your machine.
|
|
SECRET_KEY = os.getenv('SECRET_KEY', 'default-dev-secret')
|
|
|
|
# SECURITY WARNING: don't run with debug turned on in production!
|
|
DEBUG = bool(os.getenv('DEBUG', False))
|
|
|
|
TESTING = sys.argv[1:2] == ['test']
|
|
|
|
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'id.local').split(',')
|
|
|
|
SITE_ID = 1
|
|
|
|
INSTALLED_APPS = [
|
|
"django.contrib.admin",
|
|
"django.contrib.admindocs",
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.humanize",
|
|
"django.contrib.sessions",
|
|
"django.contrib.messages",
|
|
"django.contrib.staticfiles",
|
|
"django.contrib.sites",
|
|
"django.contrib.flatpages",
|
|
"oauth2_provider",
|
|
"pipeline",
|
|
"sorl.thumbnail",
|
|
"django_admin_select2",
|
|
"loginas",
|
|
"bid_main",
|
|
"bid_api",
|
|
"bid_addon_support",
|
|
"background_task",
|
|
]
|
|
|
|
MIDDLEWARE = [
|
|
"django.middleware.security.SecurityMiddleware",
|
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
"django.middleware.common.CommonMiddleware",
|
|
"django.middleware.csrf.CsrfViewMiddleware",
|
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
"bid_main.middleware.user_session_middleware",
|
|
"django.contrib.messages.middleware.MessageMiddleware",
|
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
"oauth2_provider.middleware.OAuth2TokenMiddleware",
|
|
]
|
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
"oauth2_provider.backends.OAuth2Backend",
|
|
"django.contrib.auth.backends.ModelBackend",
|
|
]
|
|
|
|
ROOT_URLCONF = "blenderid.urls"
|
|
|
|
TEMPLATES = [
|
|
{
|
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
"DIRS": [
|
|
BASE_DIR / "templates",
|
|
BASE_DIR / "assets_shared" / "src" / "templates",
|
|
],
|
|
"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",
|
|
"bid_main.context_processors.settings",
|
|
],
|
|
'loaders': {
|
|
(
|
|
'pypugjs.ext.django.Loader',
|
|
(
|
|
'django.template.loaders.filesystem.Loader',
|
|
'django.template.loaders.app_directories.Loader',
|
|
),
|
|
)
|
|
},
|
|
'builtins': [
|
|
'pypugjs.ext.django.templatetags',
|
|
],
|
|
},
|
|
},
|
|
]
|
|
|
|
WSGI_APPLICATION = "blenderid.wsgi.application"
|
|
|
|
DATABASES = {
|
|
'default': dj_database_url.config(default='sqlite:///{}'.format(BASE_DIR / 'db.sqlite3')),
|
|
}
|
|
DATABASES['default']['CONN_MAX_AGE'] = 600
|
|
|
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
|
|
|
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",
|
|
},
|
|
]
|
|
|
|
DATE_FORMAT = "d-b-Y (D)"
|
|
DATETIME_FORMAT = "d-b-Y, H:i"
|
|
SHORT_DATE_FORMAT = "Y-m-d"
|
|
SHORT_DATETIME_FORMAT = f"{SHORT_DATE_FORMAT}, H:i"
|
|
TIME_FORMAT = "H:i:s"
|
|
TIME_ZONE = "CET"
|
|
USE_TZ = True
|
|
|
|
LANGUAGE_CODE = "en-us"
|
|
USE_I18N = True
|
|
USE_L10N = False
|
|
|
|
PIPELINE = {
|
|
'JS_COMPRESSOR': 'pipeline.compressors.jsmin.JSMinCompressor',
|
|
'CSS_COMPRESSOR': 'pipeline.compressors.NoopCompressor',
|
|
'JAVASCRIPT': {
|
|
'notifications': {
|
|
'source_filenames': [
|
|
'bid_main/scripts/notifications.js',
|
|
],
|
|
'output_filename': 'js/notifications.js',
|
|
'extra_context': {'async': False, 'defer': False},
|
|
},
|
|
'web-assets': {
|
|
'source_filenames': ('tutti/10_navbar.js',),
|
|
'output_filename': 'js/web-assets.js',
|
|
'extra_context': {'async': True, 'defer': True},
|
|
},
|
|
},
|
|
'STYLESHEETS': {
|
|
'main': {
|
|
'source_filenames': ('bid_main/styles/main.sass',),
|
|
'output_filename': 'css/main.css',
|
|
'extra_context': {'media': 'screen,projection'},
|
|
},
|
|
},
|
|
'COMPILERS': ('libsasscompiler.LibSassCompiler',),
|
|
'DISABLE_WRAPPER': True,
|
|
}
|
|
|
|
# Static files (CSS, JavaScript, Images)
|
|
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
|
STATIC_URL = "/static/"
|
|
STATIC_ROOT = os.getenv('STATIC_ROOT', BASE_DIR / "static")
|
|
|
|
STATICFILES_DIRS = [
|
|
str(BASE_DIR / 'assets_shared'),
|
|
str(BASE_DIR / 'assets_shared/src/scripts/'),
|
|
]
|
|
|
|
STATICFILES_FINDERS = [
|
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
|
'pipeline.finders.PipelineFinder',
|
|
]
|
|
STATICFILES_STORAGE = 'pipeline.storage.PipelineManifestStorage'
|
|
|
|
# Uploaded files
|
|
# https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-MEDIA_ROOT
|
|
MEDIA_URL = "/media/"
|
|
MEDIA_ROOT = os.getenv('MEDIA_ROOT', BASE_DIR / "media") # may not be inside STATIC_ROOT
|
|
|
|
with open(BASE_DIR / "oidc.key", "r") as file:
|
|
# Can be generated with 'openssl genrsa -out oidc.key 4096'
|
|
oidc_rsa_private_key = file.read()
|
|
|
|
# Defining one of those means you have to define them all.
|
|
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "bid_main.OAuth2AccessToken"
|
|
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "bid_main.OAuth2RefreshToken"
|
|
OAUTH2_PROVIDER_APPLICATION_MODEL = "bid_main.OAuth2Application"
|
|
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "oauth2_provider.IDToken"
|
|
|
|
OAUTH2_PROVIDER = {
|
|
"OAUTH2_VALIDATOR_CLASS": "bid_main.oauth_validator.BlenderIDOAuth2Validator",
|
|
"OIDC_ENABLED": True,
|
|
"OIDC_RSA_PRIVATE_KEY": oidc_rsa_private_key,
|
|
"SCOPES": {
|
|
"email": "Default scope",
|
|
"badge": "Read access to the user's badges",
|
|
"openid": "OpenID Connect scope",
|
|
"profile": "Profile details such as name and nickname",
|
|
},
|
|
"PKCE_REQUIRED": False,
|
|
"ALLOWED_REDIRECT_URI_SCHEMES": ["https"],
|
|
"REQUEST_APPROVAL_PROMPT": "auto",
|
|
"ACCESS_TOKEN_EXPIRE_SECONDS": 3600 * 24 * 31, # keep for a month
|
|
"REFRESH_TOKEN_EXPIRE_SECONDS": 3600 * 24 * 31, # keep for a month
|
|
"ACCESS_TOKEN_MODEL": OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL,
|
|
"REFRESH_TOKEN_MODEL": OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL,
|
|
"APPLICATION_MODEL": OAUTH2_PROVIDER_APPLICATION_MODEL,
|
|
}
|
|
|
|
# This is required for compatibility with Blender Cloud, as it performs
|
|
# a POST request to /oauth/token.
|
|
APPEND_SLASH = False
|
|
|
|
# Read https://docs.djangoproject.com/en/1.9/topics/auth/passwords/#password-upgrading
|
|
# before removing any password hasher from this list.
|
|
PASSWORD_HASHERS = [
|
|
"bid_main.hashers.BlenderIdPasswordHasher",
|
|
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
|
|
"django.contrib.auth.hashers.BCryptPasswordHasher",
|
|
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
|
|
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
|
|
]
|
|
|
|
AUTH_USER_MODEL = "bid_main.User"
|
|
LOGIN_URL = "bid_main:login"
|
|
LOGIN_REDIRECT_URL = "bid_main:index"
|
|
LOGOUT_URL = reverse_lazy("bid_main:logout")
|
|
|
|
# Hosts that we allow redirecting to with a next=xxx parameter on the /login and /switch
|
|
# endpoints. This is a limited set for security reasons.
|
|
NEXT_REDIR_AFTER_LOGIN_ALLOWED_HOSTS = {
|
|
"cloud.local:5000",
|
|
"cloud.local:5001",
|
|
"cloud.blender.org",
|
|
"blender.cloud",
|
|
"blender.community",
|
|
}
|
|
|
|
CSRF_COOKIE_SECURE = True
|
|
CSRF_FAILURE_VIEW = "bid_main.views.errors.csrf_failure"
|
|
CSRF_TRUSTED_ORIGINS = ['https://*.blender.org']
|
|
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
|
|
SESSION_COOKIE_SECURE = True
|
|
|
|
# Privacy Policy date; anyone who agreed to the privacy policy before this date
|
|
# (or never) will be presented with an agreement prompt and has to agree before
|
|
# being able to use the website.
|
|
PPDATE = datetime.datetime(2018, 5, 18, 0, 0, 0, tzinfo=pytz.utc)
|
|
|
|
AVATAR_ALLOWED_FILE_EXTS = {".jpeg", ".jpg", ".png", ".webp"}
|
|
# Make sure this is less than the client_max_body_size nginx setting:
|
|
AVATAR_MAX_SIZE_BYTES = 2 * 1024**2
|
|
AVATAR_DEFAULT_FILENAME = "assets/images/default_user_avatar.png"
|
|
AVATAR_CONTENT_TYPE = "image/jpeg"
|
|
AVATAR_DEFAULT_SIZE_PIXELS = 160
|
|
# **N.B.**: thumbnail sizes are not intended to just be changed on the fly:
|
|
# thumbnails of existing images must exist in MEDIA_ROOT before
|
|
# the code expecting the new dimensions can be deployed!
|
|
AVATAR_THUMBNAIL_SIZES = [AVATAR_DEFAULT_SIZE_PIXELS, 128, 32]
|
|
THUMBNAIL_FORMAT = "JPEG"
|
|
THUMBNAIL_QUALITY = 83
|
|
|
|
GOOGLE_ANALYTICS_TRACKING_ID = os.getenv('GOOGLE_ANALYTICS_TRACKING_ID', '')
|
|
GOOGLE_RECAPTCHA_SITE_KEY = os.getenv('GOOGLE_RECAPTCHA_SITE_KEY', '')
|
|
GOOGLE_RECAPTCHA_SECRET_KEY = os.getenv('GOOGLE_RECAPTCHA_SECRET_KEY', '')
|
|
|
|
# Background tasks settings
|
|
MAX_ATTEMPTS = 5
|
|
|
|
BLENDER_ID_ADDON_CLIENT_ID = os.getenv('BLENDER_ID_ADDON_CLIENT_ID', 'SPECIAL-SNOWFLAKE-57')
|
|
|
|
# For testing purposes, allow HTTP as well as HTTPS. Never enable this in production!
|
|
# FIXME, add if DEBUG:
|
|
# now we have some http redirect uris in production
|
|
# they MUST go away
|
|
OAUTH2_PROVIDER['ALLOWED_REDIRECT_URI_SCHEMES'] = ['http', 'https']
|
|
|
|
LOGGING = {
|
|
"version": 1,
|
|
"disable_existing_loggers": True,
|
|
"formatters": {
|
|
"default": {"format": "%(asctime)-15s %(levelname)8s %(name)s %(message)s"},
|
|
"verbose": {
|
|
"format": "%(asctime)-15s %(levelname)8s %(name)s %(process)d %(thread)d %(message)s"
|
|
},
|
|
},
|
|
"handlers": {
|
|
"console": {
|
|
"class": "logging.StreamHandler",
|
|
"formatter": "default", # Set to 'verbose' in production
|
|
"stream": "ext://sys.stderr",
|
|
},
|
|
'mail_admins': {
|
|
'level': 'ERROR',
|
|
'class': 'django.utils.log.AdminEmailHandler',
|
|
'include_html': True,
|
|
},
|
|
},
|
|
"loggers": {
|
|
"bid_main": {"level": "DEBUG"},
|
|
"blenderid": {"level": "DEBUG"},
|
|
"bid_api": {"level": "DEBUG"},
|
|
"bid_addon_support": {"level": "DEBUG"},
|
|
},
|
|
"root": {
|
|
"level": "WARNING",
|
|
"handlers": [
|
|
"console",
|
|
"mail_admins",
|
|
],
|
|
},
|
|
}
|
|
|
|
if TESTING:
|
|
# Disable logging output when running tests
|
|
LOGGING = {
|
|
'version': 1,
|
|
'loggers': {
|
|
'': {
|
|
'level': 'CRITICAL',
|
|
},
|
|
},
|
|
}
|
|
|
|
# For Debug Toolbar, extend with whatever address you use to connect
|
|
# to your dev server.
|
|
if DEBUG:
|
|
CSRF_COOKIE_SECURE = False
|
|
INSTALLED_APPS += ['debug_toolbar']
|
|
INTERNAL_IPS = ["127.0.0.1"]
|
|
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
|
|
SESSION_COOKIE_SECURE = False
|
|
|
|
if SENTRY_DSN := os.getenv('SENTRY_DSN'):
|
|
import logging
|
|
import sentry_sdk
|
|
from sentry_sdk.integrations.django import DjangoIntegration
|
|
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
|
|
# By default Sentry only sends ERROR level and above.
|
|
sentry_logging = LoggingIntegration(
|
|
level=logging.INFO, # Capture this level and above as breadcrumbs
|
|
event_level=logging.WARNING # Send this level and above as events
|
|
)
|
|
sentry_sdk.init(
|
|
dsn=SENTRY_DSN,
|
|
integrations=[sentry_logging, DjangoIntegration()],
|
|
send_default_pii=False,
|
|
)
|
|
|
|
# For development, dump email to the console instead of trying to actually send it.
|
|
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'Blender ID <noreply@id.blender.org>')
|
|
EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend')
|
|
if os.environ.get('EMAIL_HOST') is not None:
|
|
EMAIL_HOST = os.getenv('EMAIL_HOST')
|
|
if os.environ.get('EMAIL_PORT') is not None:
|
|
EMAIL_PORT = os.getenv('EMAIL_PORT')
|
|
if os.environ.get('EMAIL_HOST_USER') is not None:
|
|
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
|
|
if os.environ.get('EMAIL_HOST_PASSWORD') is not None:
|
|
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
|
|
|
|
if os.environ.get('ADMINS') is not None:
|
|
# Expects the following format:
|
|
# ADMINS='J Doe: jane@example.com, John Dee: john@example.com'
|
|
ADMINS = [[_.strip() for _ in adm.split(':')] for adm in os.environ.get('ADMINS').split(',')]
|
|
MAIL_SUBJECT_PREFIX = '[Blender ID]'
|
|
SERVER_EMAIL = f'django@{ALLOWED_HOSTS[0]}'
|