funkwhale/api/config/settings/common.py

963 wiersze
35 KiB
Python
Czysty Zwykły widok Historia

# -*- coding: utf-8 -*-
"""
Django settings for funkwhale_api project.
For more information on this file, see
https://docs.djangoproject.com/en/dev/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/dev/ref/settings/
"""
from __future__ import absolute_import, unicode_literals
2018-06-10 09:29:24 +00:00
import datetime
import logging.config
import sys
from urllib.parse import urlsplit
2018-06-10 09:29:24 +00:00
import environ
from celery.schedules import crontab
2018-02-17 20:25:21 +00:00
from funkwhale_api import __version__
logger = logging.getLogger("funkwhale_api.config")
ROOT_DIR = environ.Path(__file__) - 3 # (/a/b/myfile.py - 3 = /)
2018-06-09 13:36:16 +00:00
APPS_DIR = ROOT_DIR.path("funkwhale_api")
env = environ.Env()
LOGLEVEL = env("LOGLEVEL", default="info").upper()
LOGGING_CONFIG = None
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"console": {"format": "%(asctime)s %(name)-12s %(levelname)-8s %(message)s"}
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "console"},
# # Add Handler for Sentry for `warning` and above
# 'sentry': {
# 'level': 'WARNING',
# 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
# },
},
"loggers": {
"funkwhale_api": {
2019-06-24 13:57:01 +00:00
"level": LOGLEVEL,
"handlers": ["console"],
# required to avoid double logging with root logger
"propagate": False,
},
"": {"level": "WARNING", "handlers": ["console"]},
},
}
)
env_file = env("ENV_FILE", default=None)
if env_file:
logger.info("Loading specified env file at %s", env_file)
# we have an explicitely specified env file
# so we try to load and it fail loudly if it does not exist
env.read_env(env_file)
else:
# we try to load from .env and config/.env
# but do not crash if those files don't exist
paths = [
# /srv/funwhale/api/.env
ROOT_DIR,
# /srv/funwhale/config/.env
((ROOT_DIR - 1) + "config"),
]
for path in paths:
try:
env_path = path.file(".env")
except FileNotFoundError:
logger.debug("No env file found at %s/.env", path)
continue
env.read_env(env_path)
logger.info("Loaded env file at %s/.env", path)
break
FUNKWHALE_PLUGINS_PATH = env(
"FUNKWHALE_PLUGINS_PATH", default="/srv/funkwhale/plugins/"
)
sys.path.append(FUNKWHALE_PLUGINS_PATH)
FUNKWHALE_HOSTNAME = None
2018-06-09 13:36:16 +00:00
FUNKWHALE_HOSTNAME_SUFFIX = env("FUNKWHALE_HOSTNAME_SUFFIX", default=None)
FUNKWHALE_HOSTNAME_PREFIX = env("FUNKWHALE_HOSTNAME_PREFIX", default=None)
if FUNKWHALE_HOSTNAME_PREFIX and FUNKWHALE_HOSTNAME_SUFFIX:
# We're in traefik case, in development
2018-06-09 13:36:16 +00:00
FUNKWHALE_HOSTNAME = "{}.{}".format(
FUNKWHALE_HOSTNAME_PREFIX, FUNKWHALE_HOSTNAME_SUFFIX
)
FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
else:
try:
2018-06-09 13:36:16 +00:00
FUNKWHALE_HOSTNAME = env("FUNKWHALE_HOSTNAME")
FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
except Exception:
2018-06-09 13:36:16 +00:00
FUNKWHALE_URL = env("FUNKWHALE_URL")
_parsed = urlsplit(FUNKWHALE_URL)
FUNKWHALE_HOSTNAME = _parsed.netloc
FUNKWHALE_PROTOCOL = _parsed.scheme
FUNKWHALE_PROTOCOL = FUNKWHALE_PROTOCOL.lower()
FUNKWHALE_HOSTNAME = FUNKWHALE_HOSTNAME.lower()
2018-06-09 13:36:16 +00:00
FUNKWHALE_URL = "{}://{}".format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
FUNKWHALE_SPA_HTML_ROOT = env(
"FUNKWHALE_SPA_HTML_ROOT", default=FUNKWHALE_URL + "/front/"
)
FUNKWHALE_SPA_HTML_CACHE_DURATION = env.int(
"FUNKWHALE_SPA_HTML_CACHE_DURATION", default=60 * 15
)
FUNKWHALE_EMBED_URL = env(
"FUNKWHALE_EMBED_URL", default=FUNKWHALE_URL + "/front/embed.html"
)
FUNKWHALE_SPA_REWRITE_MANIFEST = env.bool(
"FUNKWHALE_SPA_REWRITE_MANIFEST", default=True
)
FUNKWHALE_SPA_REWRITE_MANIFEST_URL = env.bool(
"FUNKWHALE_SPA_REWRITE_MANIFEST_URL", default=None
)
APP_NAME = "Funkwhale"
# XXX: deprecated, see #186
2018-06-09 13:36:16 +00:00
FEDERATION_ENABLED = env.bool("FEDERATION_ENABLED", default=True)
FEDERATION_HOSTNAME = env("FEDERATION_HOSTNAME", default=FUNKWHALE_HOSTNAME).lower()
# XXX: deprecated, see #186
2018-06-09 13:36:16 +00:00
FEDERATION_COLLECTION_PAGE_SIZE = env.int("FEDERATION_COLLECTION_PAGE_SIZE", default=50)
# XXX: deprecated, see #186
FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool(
2018-06-09 13:36:16 +00:00
"FEDERATION_MUSIC_NEEDS_APPROVAL", default=True
)
# XXX: deprecated, see #186
2018-06-09 13:36:16 +00:00
FEDERATION_ACTOR_FETCH_DELAY = env.int("FEDERATION_ACTOR_FETCH_DELAY", default=60 * 12)
2019-01-30 10:54:43 +00:00
FEDERATION_SERVICE_ACTOR_USERNAME = env(
"FEDERATION_SERVICE_ACTOR_USERNAME", default="service"
)
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[]) + [FUNKWHALE_HOSTNAME]
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = (
2018-06-09 13:36:16 +00:00
"channels",
# Default Django apps:
2018-06-09 13:36:16 +00:00
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.postgres",
# Useful template tags:
# 'django.contrib.humanize',
# Admin
2018-06-09 13:36:16 +00:00
"django.contrib.admin",
)
THIRD_PARTY_APPS = (
# 'crispy_forms', # Form layouts
2018-06-09 13:36:16 +00:00
"allauth", # registration
"allauth.account", # registration
"allauth.socialaccount", # registration
"corsheaders",
"oauth2_provider",
2018-06-09 13:36:16 +00:00
"rest_framework",
"rest_framework.authtoken",
"rest_auth",
"rest_auth.registration",
"dynamic_preferences",
"django_filters",
"django_cleanup",
2018-07-13 12:10:39 +00:00
"versatileimagefield",
)
2018-02-17 20:25:21 +00:00
# Sentry
RAVEN_ENABLED = env.bool("RAVEN_ENABLED", default=False)
2018-06-09 13:36:16 +00:00
RAVEN_DSN = env("RAVEN_DSN", default="")
2018-02-17 20:25:21 +00:00
if RAVEN_ENABLED:
RAVEN_CONFIG = {
2018-06-09 13:36:16 +00:00
"dsn": RAVEN_DSN,
2018-02-17 20:25:21 +00:00
# If you are using git, you can also automatically configure the
# release based on the git info.
2018-06-09 13:36:16 +00:00
"release": __version__,
2018-02-17 20:25:21 +00:00
}
2018-06-09 13:36:16 +00:00
THIRD_PARTY_APPS += ("raven.contrib.django.raven_compat",)
2018-02-17 20:25:21 +00:00
# Apps specific for this project go here.
LOCAL_APPS = (
"funkwhale_api.common.apps.CommonConfig",
2018-06-09 13:36:16 +00:00
"funkwhale_api.activity.apps.ActivityConfig",
"funkwhale_api.users", # custom users app
"funkwhale_api.users.oauth",
# Your stuff: custom apps go here
2018-06-09 13:36:16 +00:00
"funkwhale_api.instance",
"funkwhale_api.audio",
2018-06-09 13:36:16 +00:00
"funkwhale_api.music",
"funkwhale_api.requests",
"funkwhale_api.favorites",
"funkwhale_api.federation",
"funkwhale_api.moderation.apps.ModerationConfig",
2018-06-09 13:36:16 +00:00
"funkwhale_api.radios",
"funkwhale_api.history",
"funkwhale_api.playlists",
"funkwhale_api.subsonic",
2019-07-08 13:26:14 +00:00
"funkwhale_api.tags",
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
2018-02-17 20:25:21 +00:00
PLUGINS = [p for p in env.list("FUNKWHALE_PLUGINS", default=[]) if p]
if PLUGINS:
logger.info("Running with the following plugins enabled: %s", ", ".join(PLUGINS))
else:
logger.info("Running with no plugins")
ADDITIONAL_APPS = env.list("ADDITIONAL_APPS", default=[])
INSTALLED_APPS = (
DJANGO_APPS
+ THIRD_PARTY_APPS
+ LOCAL_APPS
+ tuple(["{}.apps.Plugin".format(p) for p in PLUGINS])
+ tuple(ADDITIONAL_APPS)
)
# MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------
ADDITIONAL_MIDDLEWARES_BEFORE = env.list("ADDITIONAL_MIDDLEWARES_BEFORE", default=[])
MIDDLEWARE = tuple(ADDITIONAL_MIDDLEWARES_BEFORE) + (
"django.middleware.security.SecurityMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
"funkwhale_api.common.middleware.SPAFallbackMiddleware",
2018-06-09 13:36:16 +00:00
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"funkwhale_api.users.middleware.RecordActivityMiddleware",
"funkwhale_api.common.middleware.ThrottleStatusMiddleware",
)
# DEBUG
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = env.bool("DJANGO_DEBUG", False)
# FIXTURE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
2018-06-09 13:36:16 +00:00
FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),)
# EMAIL CONFIGURATION
# ------------------------------------------------------------------------------
# EMAIL
# ------------------------------------------------------------------------------
DEFAULT_FROM_EMAIL = env(
2018-06-09 13:36:16 +00:00
"DEFAULT_FROM_EMAIL", default="Funkwhale <noreply@{}>".format(FUNKWHALE_HOSTNAME)
)
2018-06-09 13:36:16 +00:00
EMAIL_SUBJECT_PREFIX = env("EMAIL_SUBJECT_PREFIX", default="[Funkwhale] ")
SERVER_EMAIL = env("SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
2018-06-09 13:36:16 +00:00
EMAIL_CONFIG = env.email_url("EMAIL_CONFIG", default="consolemail://")
vars().update(EMAIL_CONFIG)
# DATABASE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
2018-06-09 13:36:16 +00:00
"default": env.db("DATABASE_URL")
}
2018-06-09 13:36:16 +00:00
DATABASES["default"]["ATOMIC_REQUESTS"] = True
2019-10-17 12:39:06 +00:00
DATABASES["default"]["CONN_MAX_AGE"] = env("DB_CONN_MAX_AGE", default=60 * 5)
MIGRATION_MODULES = {
# see https://github.com/jazzband/django-oauth-toolkit/issues/634
# swappable models are badly designed in oauth2_provider
# ignore migrations and provide our own models.
2019-04-04 14:05:34 +00:00
"oauth2_provider": None,
"sites": "funkwhale_api.contrib.sites.migrations",
}
#
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': 'db.sqlite3',
# }
# }
# GENERAL CONFIGURATION
# ------------------------------------------------------------------------------
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
2018-06-09 13:36:16 +00:00
TIME_ZONE = "UTC"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
2018-06-09 13:36:16 +00:00
LANGUAGE_CODE = "en-us"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
# TEMPLATE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES = [
{
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
2018-06-09 13:36:16 +00:00
"BACKEND": "django.template.backends.django.DjangoTemplates",
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
2018-06-09 13:36:16 +00:00
"DIRS": [str(APPS_DIR.path("templates"))],
"OPTIONS": {
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
2018-06-09 13:36:16 +00:00
"debug": DEBUG,
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
2018-06-09 13:36:16 +00:00
"loaders": [
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
2018-06-09 13:36:16 +00:00
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
# Your stuff: custom template context processors go here
],
},
2018-06-09 13:36:16 +00:00
}
]
# See: http://django-crispy-forms.readthedocs.org/en/latest/install.html#template-packs
2018-06-09 13:36:16 +00:00
CRISPY_TEMPLATE_PACK = "bootstrap3"
# STATIC FILE CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
2018-06-09 13:36:16 +00:00
STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR("staticfiles")))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = env("STATIC_URL", default=FUNKWHALE_URL + "/staticfiles/")
2018-06-09 13:36:16 +00:00
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage"
PROXY_MEDIA = env.bool("PROXY_MEDIA", default=True)
AWS_DEFAULT_ACL = None
AWS_QUERYSTRING_AUTH = env.bool("AWS_QUERYSTRING_AUTH", default=not PROXY_MEDIA)
AWS_S3_MAX_MEMORY_SIZE = env.int(
"AWS_S3_MAX_MEMORY_SIZE", default=1000 * 1000 * 1000 * 20
)
AWS_QUERYSTRING_EXPIRE = env.int("AWS_QUERYSTRING_EXPIRE", default=3600)
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default=None)
if AWS_ACCESS_KEY_ID:
AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL", default=None)
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default=None)
AWS_S3_SIGNATURE_VERSION = "s3v4"
AWS_LOCATION = env("AWS_LOCATION", default="")
DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIS3Boto3Storage"
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
2018-06-09 13:36:16 +00:00
STATICFILES_DIRS = (str(APPS_DIR.path("static")),)
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
2018-06-09 13:36:16 +00:00
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
)
# MEDIA CONFIGURATION
# ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
2018-06-09 13:36:16 +00:00
MEDIA_ROOT = env("MEDIA_ROOT", default=str(APPS_DIR("media")))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = env("MEDIA_URL", default=FUNKWHALE_URL + "/media/")
FILE_UPLOAD_PERMISSIONS = 0o644
2019-11-25 08:49:06 +00:00
ATTACHMENTS_UNATTACHED_PRUNE_DELAY = env.int(
"ATTACHMENTS_UNATTACHED_PRUNE_DELAY", default=3600 * 24
)
# URL Configuration
# ------------------------------------------------------------------------------
2018-06-09 13:36:16 +00:00
ROOT_URLCONF = "config.urls"
SPA_URLCONF = "config.spa_urls"
2018-02-25 12:05:01 +00:00
ASGI_APPLICATION = "config.routing.application"
# This ensures that Django will be able to detect a secure connection
2018-06-09 13:36:16 +00:00
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# AUTHENTICATION CONFIGURATION
# ------------------------------------------------------------------------------
AUTHENTICATION_BACKENDS = (
"funkwhale_api.users.auth_backends.ModelBackend",
2018-06-09 13:36:16 +00:00
"allauth.account.auth_backends.AuthenticationBackend",
)
SESSION_COOKIE_HTTPONLY = False
# Some really nice defaults
2018-06-09 13:36:16 +00:00
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_EMAIL_REQUIRED = True
2018-06-09 13:36:16 +00:00
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_USERNAME_VALIDATORS = "funkwhale_api.users.serializers.username_validators"
# Custom user app defaults
# Select the correct user model
2018-06-09 13:36:16 +00:00
AUTH_USER_MODEL = "users.User"
LOGIN_REDIRECT_URL = "users:redirect"
LOGIN_URL = "account_login"
# OAuth configuration
from funkwhale_api.users.oauth import scopes # noqa
OAUTH2_PROVIDER = {
"SCOPES": {s.id: s.label for s in scopes.SCOPES_BY_ID.values()},
"ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https", "urn"],
# we keep expired tokens for 15 days, for tracability
"REFRESH_TOKEN_EXPIRE_SECONDS": 3600 * 24 * 15,
"AUTHORIZATION_CODE_EXPIRE_SECONDS": 5 * 60,
"ACCESS_TOKEN_EXPIRE_SECONDS": 60 * 60 * 10,
2019-05-03 10:23:45 +00:00
"OAUTH2_SERVER_CLASS": "funkwhale_api.users.oauth.server.OAuth2Server",
}
OAUTH2_PROVIDER_APPLICATION_MODEL = "users.Application"
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "users.AccessToken"
OAUTH2_PROVIDER_GRANT_MODEL = "users.Grant"
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "users.RefreshToken"
2018-08-22 18:10:39 +00:00
# LDAP AUTHENTICATION CONFIGURATION
# ------------------------------------------------------------------------------
AUTH_LDAP_ENABLED = env.bool("LDAP_ENABLED", default=False)
if AUTH_LDAP_ENABLED:
# Import the LDAP modules here; this way, we don't need the dependency unless someone
# actually enables the LDAP support
import ldap
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion, GroupOfNamesType
# Add LDAP to the authentication backends
AUTHENTICATION_BACKENDS += ("django_auth_ldap.backend.LDAPBackend",)
# Basic configuration
AUTH_LDAP_SERVER_URI = env("LDAP_SERVER_URI")
AUTH_LDAP_BIND_DN = env("LDAP_BIND_DN", default="")
AUTH_LDAP_BIND_PASSWORD = env("LDAP_BIND_PASSWORD", default="")
AUTH_LDAP_SEARCH_FILTER = env("LDAP_SEARCH_FILTER", default="(uid={0})").format(
"%(user)s"
)
AUTH_LDAP_START_TLS = env.bool("LDAP_START_TLS", default=False)
AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = env(
"AUTH_LDAP_BIND_AS_AUTHENTICATING_USER", default=False
)
2018-08-22 18:10:39 +00:00
DEFAULT_USER_ATTR_MAP = [
"first_name:givenName",
"last_name:sn",
"username:cn",
"email:mail",
]
LDAP_USER_ATTR_MAP = env.list("LDAP_USER_ATTR_MAP", default=DEFAULT_USER_ATTR_MAP)
AUTH_LDAP_USER_ATTR_MAP = {}
for m in LDAP_USER_ATTR_MAP:
funkwhale_field, ldap_field = m.split(":")
AUTH_LDAP_USER_ATTR_MAP[funkwhale_field.strip()] = ldap_field.strip()
# Determine root DN supporting multiple root DNs
AUTH_LDAP_ROOT_DN = env("LDAP_ROOT_DN")
AUTH_LDAP_ROOT_DN_LIST = []
for ROOT_DN in AUTH_LDAP_ROOT_DN.split():
AUTH_LDAP_ROOT_DN_LIST.append(
LDAPSearch(ROOT_DN, ldap.SCOPE_SUBTREE, AUTH_LDAP_SEARCH_FILTER)
)
# Search for the user in all the root DNs
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*AUTH_LDAP_ROOT_DN_LIST)
# Search for group types
LDAP_GROUP_DN = env("LDAP_GROUP_DN", default="")
if LDAP_GROUP_DN:
AUTH_LDAP_GROUP_DN = LDAP_GROUP_DN
# Get filter
AUTH_LDAP_GROUP_FILTER = env("LDAP_GROUP_FILER", default="")
# Search for the group in the specified DN
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
AUTH_LDAP_GROUP_DN, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_FILTER
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType()
# Configure basic group support
LDAP_REQUIRE_GROUP = env("LDAP_REQUIRE_GROUP", default="")
if LDAP_REQUIRE_GROUP:
AUTH_LDAP_REQUIRE_GROUP = LDAP_REQUIRE_GROUP
LDAP_DENY_GROUP = env("LDAP_DENY_GROUP", default="")
if LDAP_DENY_GROUP:
AUTH_LDAP_DENY_GROUP = LDAP_DENY_GROUP
# SLUGLIFIER
2018-06-09 13:36:16 +00:00
AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify"
2017-07-17 20:00:32 +00:00
CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
CACHES = {
"default": env.cache_url("CACHE_URL", default=CACHE_DEFAULT),
"local": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "local-cache",
},
}
2017-07-17 20:00:32 +00:00
CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
2018-06-09 13:36:16 +00:00
2018-02-25 12:05:01 +00:00
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {"hosts": [CACHES["default"]["LOCATION"]]},
2018-06-09 13:36:16 +00:00
}
2018-02-25 12:05:01 +00:00
}
2017-07-17 20:00:32 +00:00
CACHES["default"]["OPTIONS"] = {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"IGNORE_EXCEPTIONS": True, # mimics memcache behavior.
2018-06-09 13:36:16 +00:00
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
2017-07-17 20:00:32 +00:00
}
2019-10-25 08:23:13 +00:00
CACHEOPS_DURATION = env("CACHEOPS_DURATION", default=0)
CACHEOPS_ENABLED = bool(CACHEOPS_DURATION)
if CACHEOPS_ENABLED:
INSTALLED_APPS += ("cacheops",)
CACHEOPS_REDIS = env("CACHE_URL", default=CACHE_DEFAULT)
CACHEOPS_PREFIX = lambda _: "cacheops" # noqa
CACHEOPS_DEFAULTS = {"timeout": CACHEOPS_DURATION}
CACHEOPS = {
"music.album": {"ops": "count"},
"music.artist": {"ops": "count"},
"music.track": {"ops": "count"},
}
2017-07-17 20:00:32 +00:00
# CELERY
2018-06-09 13:36:16 +00:00
INSTALLED_APPS += ("funkwhale_api.taskapp.celery.CeleryConfig",)
CELERY_BROKER_URL = env(
2018-06-09 13:36:16 +00:00
"CELERY_BROKER_URL", default=env("CACHE_URL", default=CACHE_DEFAULT)
)
# END CELERY
# Location of root django.contrib.admin URL, use {% url 'admin:index' %}
2018-03-06 16:44:53 +00:00
# Your common stuff: Below this line define 3rd party library settings
CELERY_TASK_DEFAULT_RATE_LIMIT = 1
CELERY_TASK_TIME_LIMIT = 300
CELERY_BEAT_SCHEDULE = {
2019-11-25 08:49:06 +00:00
"common.prune_unattached_attachments": {
"task": "common.prune_unattached_attachments",
"schedule": crontab(minute="0", hour="*"),
"options": {"expires": 60 * 60},
},
2018-06-09 13:36:16 +00:00
"federation.clean_music_cache": {
"task": "federation.clean_music_cache",
"schedule": crontab(minute="0", hour="*/2"),
2018-06-09 13:36:16 +00:00
"options": {"expires": 60 * 2},
2018-10-24 17:44:31 +00:00
},
"music.clean_transcoding_cache": {
"task": "music.clean_transcoding_cache",
"schedule": crontab(minute="0", hour="*"),
2018-10-24 17:44:31 +00:00
"options": {"expires": 60 * 2},
},
"oauth.clear_expired_tokens": {
"task": "oauth.clear_expired_tokens",
"schedule": crontab(minute="0", hour="0"),
"options": {"expires": 60 * 60 * 24},
},
"federation.refresh_nodeinfo_known_nodes": {
"task": "federation.refresh_nodeinfo_known_nodes",
"schedule": crontab(
**env.dict(
"SCHEDULE_FEDERATION_REFRESH_NODEINFO_KNOWN_NODES",
default={"minute": "0", "hour": "*"},
)
),
"options": {"expires": 60 * 60},
},
}
if env.bool("ADD_ALBUM_TAGS_FROM_TRACKS", default=True):
CELERY_BEAT_SCHEDULE["music.albums_set_tags_from_tracks"] = {
"task": "music.albums_set_tags_from_tracks",
"schedule": crontab(minute="0", hour="4", day_of_week="4"),
"options": {"expires": 60 * 60 * 2},
}
if env.bool("ADD_ARTIST_TAGS_FROM_TRACKS", default=True):
CELERY_BEAT_SCHEDULE["music.artists_set_tags_from_tracks"] = {
"task": "music.artists_set_tags_from_tracks",
"schedule": crontab(minute="0", hour="4", day_of_week="4"),
"options": {"expires": 60 * 60 * 2},
}
NODEINFO_REFRESH_DELAY = env.int("NODEINFO_REFRESH_DELAY", default=3600 * 24)
def get_user_secret_key(user):
from django.conf import settings
return settings.SECRET_KEY + str(user.secret_key)
JWT_AUTH = {
2018-06-09 13:36:16 +00:00
"JWT_ALLOW_REFRESH": True,
"JWT_EXPIRATION_DELTA": datetime.timedelta(days=7),
"JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=30),
"JWT_AUTH_HEADER_PREFIX": "JWT",
"JWT_GET_USER_SECRET_KEY": get_user_secret_key,
}
OLD_PASSWORD_FIELD_ENABLED = True
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {"min_length": env.int("PASSWORD_MIN_LENGTH", default=8)},
},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
2018-06-09 13:36:16 +00:00
ACCOUNT_ADAPTER = "funkwhale_api.users.adapters.FunkwhaleAccountAdapter"
CORS_ORIGIN_ALLOW_ALL = True
# CORS_ORIGIN_WHITELIST = (
# 'localhost',
# 'funkwhale.localhost',
# )
CORS_ALLOW_CREDENTIALS = True
REST_FRAMEWORK = {
2018-06-09 13:36:16 +00:00
"DEFAULT_PAGINATION_CLASS": "funkwhale_api.common.pagination.FunkwhalePagination",
"PAGE_SIZE": 25,
"DEFAULT_PARSER_CLASSES": (
"rest_framework.parsers.JSONParser",
"rest_framework.parsers.FormParser",
"rest_framework.parsers.MultiPartParser",
"funkwhale_api.federation.parsers.ActivityParser",
),
2018-06-09 13:36:16 +00:00
"DEFAULT_AUTHENTICATION_CLASSES": (
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
2018-06-09 13:36:16 +00:00
"funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS",
"funkwhale_api.common.authentication.BearerTokenHeaderAuth",
"funkwhale_api.common.authentication.JSONWebTokenAuthentication",
2018-06-09 13:36:16 +00:00
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": (
"funkwhale_api.users.oauth.permissions.ScopePermission",
),
2018-06-09 13:36:16 +00:00
"DEFAULT_FILTER_BACKENDS": (
"rest_framework.filters.OrderingFilter",
"django_filters.rest_framework.DjangoFilterBackend",
),
2018-06-09 13:36:16 +00:00
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
"NUM_PROXIES": env.int("NUM_PROXIES", default=1),
}
THROTTLING_ENABLED = env.bool("THROTTLING_ENABLED", default=True)
if THROTTLING_ENABLED:
REST_FRAMEWORK["DEFAULT_THROTTLE_CLASSES"] = env.list(
"THROTTLE_CLASSES",
default=["funkwhale_api.common.throttling.FunkwhaleThrottle"],
)
THROTTLING_SCOPES = {
"*": {"anonymous": "anonymous-wildcard", "authenticated": "authenticated-wildcard"},
"create": {
"authenticated": "authenticated-create",
"anonymous": "anonymous-create",
},
"list": {"authenticated": "authenticated-list", "anonymous": "anonymous-list"},
"retrieve": {
"authenticated": "authenticated-retrieve",
"anonymous": "anonymous-retrieve",
},
"destroy": {
"authenticated": "authenticated-destroy",
"anonymous": "anonymous-destroy",
},
"update": {
"authenticated": "authenticated-update",
"anonymous": "anonymous-update",
},
"partial_update": {
"authenticated": "authenticated-update",
"anonymous": "anonymous-update",
},
}
THROTTLING_USER_RATES = env.dict("THROTTLING_RATES", default={})
THROTTLING_RATES = {
"anonymous-wildcard": {
"rate": THROTTLING_USER_RATES.get("anonymous-wildcard", "1000/h"),
"description": "Anonymous requests not covered by other limits",
},
"authenticated-wildcard": {
"rate": THROTTLING_USER_RATES.get("authenticated-wildcard", "2000/h"),
"description": "Authenticated requests not covered by other limits",
},
"authenticated-create": {
"rate": THROTTLING_USER_RATES.get("authenticated-create", "1000/hour"),
"description": "Authenticated POST requests",
},
"anonymous-create": {
"rate": THROTTLING_USER_RATES.get("anonymous-create", "1000/day"),
"description": "Anonymous POST requests",
},
"authenticated-list": {
"rate": THROTTLING_USER_RATES.get("authenticated-list", "10000/hour"),
"description": "Authenticated GET requests on resource lists",
},
"anonymous-list": {
"rate": THROTTLING_USER_RATES.get("anonymous-list", "10000/day"),
"description": "Anonymous GET requests on resource lists",
},
"authenticated-retrieve": {
"rate": THROTTLING_USER_RATES.get("authenticated-retrieve", "10000/hour"),
"description": "Authenticated GET requests on resource detail",
},
"anonymous-retrieve": {
"rate": THROTTLING_USER_RATES.get("anonymous-retrieve", "10000/day"),
"description": "Anonymous GET requests on resource detail",
},
"authenticated-destroy": {
"rate": THROTTLING_USER_RATES.get("authenticated-destroy", "500/hour"),
"description": "Authenticated DELETE requests on resource detail",
},
"anonymous-destroy": {
"rate": THROTTLING_USER_RATES.get("anonymous-destroy", "1000/day"),
"description": "Anonymous DELETE requests on resource detail",
},
"authenticated-update": {
"rate": THROTTLING_USER_RATES.get("authenticated-update", "1000/hour"),
"description": "Authenticated PATCH and PUT requests on resource detail",
},
"anonymous-update": {
"rate": THROTTLING_USER_RATES.get("anonymous-update", "1000/day"),
"description": "Anonymous PATCH and PUT requests on resource detail",
},
"subsonic": {
"rate": THROTTLING_USER_RATES.get("subsonic", "2000/hour"),
"description": "All subsonic API requests",
},
# potentially spammy / dangerous endpoints
"authenticated-reports": {
"rate": THROTTLING_USER_RATES.get("authenticated-reports", "100/day"),
"description": "Authenticated report submission",
},
"anonymous-reports": {
"rate": THROTTLING_USER_RATES.get("anonymous-reports", "10/day"),
"description": "Anonymous report submission",
},
"authenticated-oauth-app": {
"rate": THROTTLING_USER_RATES.get("authenticated-oauth-app", "10/hour"),
"description": "Authenticated OAuth app creation",
},
"anonymous-oauth-app": {
"rate": THROTTLING_USER_RATES.get("anonymous-oauth-app", "10/day"),
"description": "Anonymous OAuth app creation",
},
"oauth-authorize": {
"rate": THROTTLING_USER_RATES.get("oauth-authorize", "100/hour"),
"description": "OAuth app authorization",
},
"oauth-token": {
"rate": THROTTLING_USER_RATES.get("oauth-token", "100/hour"),
"description": "OAuth token creation",
},
"oauth-revoke-token": {
"rate": THROTTLING_USER_RATES.get("oauth-revoke-token", "100/hour"),
"description": "OAuth token deletion",
},
"jwt-login": {
"rate": THROTTLING_USER_RATES.get("jwt-login", "30/hour"),
"description": "JWT token creation",
},
"jwt-refresh": {
"rate": THROTTLING_USER_RATES.get("jwt-refresh", "30/hour"),
"description": "JWT token refresh",
},
"signup": {
"rate": THROTTLING_USER_RATES.get("signup", "10/day"),
"description": "Account creation",
},
"verify-email": {
"rate": THROTTLING_USER_RATES.get("verify-email", "20/h"),
"description": "Email address confirmation",
},
"password-change": {
"rate": THROTTLING_USER_RATES.get("password-change", "20/h"),
"description": "Password change (when authenticated)",
},
"password-reset": {
"rate": THROTTLING_USER_RATES.get("password-reset", "20/h"),
"description": "Password reset request",
},
"password-reset-confirm": {
"rate": THROTTLING_USER_RATES.get("password-reset-confirm", "20/h"),
"description": "Password reset confirmation",
},
}
2018-06-09 13:36:16 +00:00
BROWSABLE_API_ENABLED = env.bool("BROWSABLE_API_ENABLED", default=False)
if BROWSABLE_API_ENABLED:
2018-06-09 13:36:16 +00:00
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] += (
"rest_framework.renderers.BrowsableAPIRenderer",
)
2018-05-06 09:30:41 +00:00
REST_AUTH_SERIALIZERS = {
2018-06-09 13:36:16 +00:00
"PASSWORD_RESET_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetSerializer" # noqa
2018-05-06 09:30:41 +00:00
}
REST_SESSION_LOGIN = False
REST_USE_JWT = True
ATOMIC_REQUESTS = False
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True
# Wether we should use Apache, Nginx (or other) headers when serving audio files
# Default to Nginx
2018-06-09 13:36:16 +00:00
REVERSE_PROXY_TYPE = env("REVERSE_PROXY_TYPE", default="nginx")
assert REVERSE_PROXY_TYPE in ["apache2", "nginx"], "Unsupported REVERSE_PROXY_TYPE"
# Which path will be used to process the internal redirection
# **DO NOT** put a slash at the end
2018-06-09 13:36:16 +00:00
PROTECT_FILES_PATH = env("PROTECT_FILES_PATH", default="/_protected")
# use this setting to tweak for how long you want to cache
# musicbrainz results. (value is in seconds)
2018-06-09 13:36:16 +00:00
MUSICBRAINZ_CACHE_DURATION = env.int("MUSICBRAINZ_CACHE_DURATION", default=300)
# Use this setting to change the musicbrainz hostname, for instance to
# use a mirror. The hostname can also contain a port number (so, e.g.,
# "localhost:5000" is a valid name to set).
MUSICBRAINZ_HOSTNAME = env("MUSICBRAINZ_HOSTNAME", default="musicbrainz.org")
2017-07-17 20:00:32 +00:00
# Custom Admin URL, use {% url 'admin:index' %}
2018-06-09 13:36:16 +00:00
ADMIN_URL = env("DJANGO_ADMIN_URL", default="^api/admin/")
CSRF_USE_SESSIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
2018-03-19 11:36:15 +00:00
# Playlist settings
# XXX: deprecated, see #186
2018-06-09 13:36:16 +00:00
PLAYLISTS_MAX_TRACKS = env.int("PLAYLISTS_MAX_TRACKS", default=250)
ACCOUNT_USERNAME_BLACKLIST = [
2018-06-09 13:36:16 +00:00
"funkwhale",
"library",
"instance",
2018-06-09 13:36:16 +00:00
"test",
"status",
"root",
"admin",
"owner",
"superuser",
"staff",
"service",
2018-07-13 12:10:39 +00:00
"me",
"ghost",
"_",
2018-09-22 12:29:30 +00:00
"-",
"hello",
"contact",
2018-09-22 12:29:30 +00:00
"inbox",
"outbox",
"shared-inbox",
"shared_inbox",
"actor",
2018-06-09 13:36:16 +00:00
] + env.list("ACCOUNT_USERNAME_BLACKLIST", default=[])
EXTERNAL_REQUESTS_VERIFY_SSL = env.bool("EXTERNAL_REQUESTS_VERIFY_SSL", default=True)
2019-11-25 08:49:06 +00:00
EXTERNAL_REQUESTS_TIMEOUT = env.int("EXTERNAL_REQUESTS_TIMEOUT", default=5)
# XXX: deprecated, see #186
API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
2018-06-09 13:36:16 +00:00
MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None)
# on Docker setup, the music directory may not match the host path,
# and we need to know it for it to serve stuff properly
MUSIC_DIRECTORY_SERVE_PATH = env(
2018-06-09 13:36:16 +00:00
"MUSIC_DIRECTORY_SERVE_PATH", default=MUSIC_DIRECTORY_PATH
)
# When this is set to default=True, we need to reenable migration music/0042
# to ensure data is populated correctly on existing pods
MUSIC_USE_DENORMALIZATION = env.bool("MUSIC_USE_DENORMALIZATION", default=False)
2018-06-19 19:47:43 +00:00
USERS_INVITATION_EXPIRATION_DAYS = env.int(
"USERS_INVITATION_EXPIRATION_DAYS", default=14
)
VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = {
"square": [
("original", "url"),
("square_crop", "crop__400x400"),
("medium_square_crop", "crop__200x200"),
("small_square_crop", "crop__50x50"),
2019-11-25 08:49:06 +00:00
],
"attachment_square": [
("original", "url"),
("medium_square_crop", "crop__200x200"),
],
}
VERSATILEIMAGEFIELD_SETTINGS = {"create_images_on_demand": False}
RSA_KEY_SIZE = 2048
# for performance gain in tests, since we don't need to actually create the
# thumbnails
CREATE_IMAGE_THUMBNAILS = env.bool("CREATE_IMAGE_THUMBNAILS", default=True)
# we rotate actor keys at most every two days by default
ACTOR_KEY_ROTATION_DELAY = env.int("ACTOR_KEY_ROTATION_DELAY", default=3600 * 48)
SUBSONIC_DEFAULT_TRANSCODING_FORMAT = (
env("SUBSONIC_DEFAULT_TRANSCODING_FORMAT", default="mp3") or None
)
# extra tags will be ignored
TAGS_MAX_BY_OBJ = env.int("TAGS_MAX_BY_OBJ", default=30)
FEDERATION_OBJECT_FETCH_DELAY = env.int(
"FEDERATION_OBJECT_FETCH_DELAY", default=60 * 24 * 3
)
MODERATION_EMAIL_NOTIFICATIONS_ENABLED = env.bool(
"MODERATION_EMAIL_NOTIFICATIONS_ENABLED", default=True
)
# Delay in days after signup before we show the "support us" messages
INSTANCE_SUPPORT_MESSAGE_DELAY = env.int("INSTANCE_SUPPORT_MESSAGE_DELAY", default=15)
FUNKWHALE_SUPPORT_MESSAGE_DELAY = env.int("FUNKWHALE_SUPPORT_MESSAGE_DELAY", default=15)
# XXX Stable release: remove
USE_FULL_TEXT_SEARCH = env.bool("USE_FULL_TEXT_SEARCH", default=True)
MIN_DELAY_BETWEEN_DOWNLOADS_COUNT = env.int(
"MIN_DELAY_BETWEEN_DOWNLOADS_COUNT", default=60 * 60 * 6
)
MARKDOWN_EXTENSIONS = env.list("MARKDOWN_EXTENSIONS", default=["nl2br", "extra"])