From 4352d126b7c25ebf45a9fdfe1daad50f8fd87719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Kol=C3=A1=C5=99?= Date: Tue, 22 Feb 2022 10:32:52 +0100 Subject: [PATCH] Implementation for django-configurations (#43) * Added django-configurations * Refactored to use django-configurations fully, removed python-decouple * Typos --- .env.template | 6 +- Makefile | 2 +- docker-compose.prod.yml | 10 +- docker-compose.yml | 5 - fiesta/.env.base | 6 +- fiesta/apps/files/storage.py | 2 +- fiesta/fiesta/settings.py | 299 ---------------------------- fiesta/fiesta/settings/__init__.py | 45 +++++ fiesta/fiesta/settings/_utils.py | 12 ++ fiesta/fiesta/settings/auth.py | 75 +++++++ fiesta/fiesta/settings/db.py | 18 ++ fiesta/fiesta/settings/files.py | 32 +++ fiesta/fiesta/settings/logging.py | 35 ++++ fiesta/fiesta/settings/project.py | 81 ++++++++ fiesta/fiesta/settings/security.py | 12 ++ fiesta/fiesta/settings/templates.py | 22 ++ fiesta/manage.py | 5 +- fiesta/run.sh | 2 +- poetry.lock | 90 +++++++-- pyproject.toml | 2 +- 20 files changed, 426 insertions(+), 335 deletions(-) delete mode 100644 fiesta/fiesta/settings.py create mode 100644 fiesta/fiesta/settings/__init__.py create mode 100644 fiesta/fiesta/settings/_utils.py create mode 100644 fiesta/fiesta/settings/auth.py create mode 100644 fiesta/fiesta/settings/db.py create mode 100644 fiesta/fiesta/settings/files.py create mode 100644 fiesta/fiesta/settings/logging.py create mode 100644 fiesta/fiesta/settings/project.py create mode 100644 fiesta/fiesta/settings/security.py create mode 100644 fiesta/fiesta/settings/templates.py diff --git a/.env.template b/.env.template index 4792ffc5..f0ba067d 100644 --- a/.env.template +++ b/.env.template @@ -1,4 +1,4 @@ -SECRET_KEY=... -DEBUG=false -PUBLIC_URL=https://fiesta.localhost +DJANGO_DEBUG=false DJANGO_LOG_LEVEL=INFO +DJANGO_SECRET_KEY= +PUBLIC_URL=https://fiesta.localhost diff --git a/Makefile b/Makefile index 369b76bb..35154ff8 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ upd: ## Runs all needed docker containers in detached mode up: ## Runs all needed docker containers docker-compose up -produp: export DEBUG = False ## Runs fiesta in production mode. +produp: export DJANGO_CONFIGURATION = LocalProduction ## Runs fiesta in production mode. produp: docker-compose -f docker-compose.yml -f docker-compose.prod.yml --profile prod up --build diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e4b7a3e4..b7ac79a2 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,7 +3,7 @@ services: web: command: './manage.py collectstatic --noinput && ./manage.py runserver 0.0.0.0:8000' environment: - DEBUG: "false" + DJANGO_CONFIGURATION: Production webserver: command: [ nginx, '-g', 'daemon off;' ] @@ -22,8 +22,8 @@ services: - "443:443" # remove profiles so production env does have filled elastic search - elastic: - profiles: [ ] +# elastic: +# profiles: [ ] - wikifetcher: - profiles: [ ] +# wikifetcher: +# profiles: [ ] diff --git a/docker-compose.yml b/docker-compose.yml index 0369c96b..42b811ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,6 @@ services: - 8000 env_file: - ./fiesta/.env.base - secrets: - .env build: context: . @@ -132,10 +131,6 @@ services: - .env - wiki/.env.base -secrets: - .env: - file: ./.env - volumes: postgres_data: elastic_data: diff --git a/fiesta/.env.base b/fiesta/.env.base index 9240d90e..d73b9203 100644 --- a/fiesta/.env.base +++ b/fiesta/.env.base @@ -1,3 +1,3 @@ -BUILD_DIR=/usr/src/build -STATIC_DIR=/usr/src/static -MEDIA_DIR=/usr/src/media +DJANGO_BUILD_DIR=/usr/src/build +DJANGO_STATIC_ROOT=/usr/src/static +DJANGO_MEDIA_ROOT=/usr/src/media diff --git a/fiesta/apps/files/storage.py b/fiesta/apps/files/storage.py index 1f8ae290..46db7b1e 100644 --- a/fiesta/apps/files/storage.py +++ b/fiesta/apps/files/storage.py @@ -38,4 +38,4 @@ def upload_to(instance: BaseModel, filename: str) -> str: except AttributeError: modified = now().isoformat() - return hashlib.md5(f"{instance.pk}-{modified}".encode()).hexdigest() + return hashlib.sha256(f"{instance.pk}-{modified}".encode()).hexdigest() diff --git a/fiesta/fiesta/settings.py b/fiesta/fiesta/settings.py deleted file mode 100644 index 13af4de2..00000000 --- a/fiesta/fiesta/settings.py +++ /dev/null @@ -1,299 +0,0 @@ -""" -Django settings for fiesta project. - -Generated by 'django-admin startproject' using Django 4.0.1. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.0/ref/settings/ -""" - -from pathlib import Path - -from decouple import AutoConfig - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - -# to load .env from folder, where docker stores secrets files -config = AutoConfig("/run/secrets/") - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = config("SECRET_KEY") - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = config("DEBUG", cast=bool) - -ALLOWED_HOSTS: list[str] = [".localhost", "127.0.0.1"] - -# Application definition - -INSTALLED_APPS = [ - # Django native - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "django.contrib.sites", - "django.contrib.humanize", - "django.forms", - # Django 3rd party - "polymorphic", - "webpack_loader", - "django_htmx", - # Fiesta apps - "apps.accounts.apps.AccountsConfig", - "apps.esnaccounts", # cannot have full config Path, since allauth/socialaccount/providers/__init__.py:38 sucks - "apps.esncards.apps.ESNcardsConfig", - "apps.fiestaforms.apps.FiestaformsConfig", - "apps.plugins.apps.PluginsConfig", - "apps.sections.apps.SectionsConfig", - "apps.universities.apps.UniversitiesConfig", - "apps.utils.apps.UtilsConfig", - "apps.wiki.apps.WikiConfig", - # Debugs - "django_extensions", - # django-allauth - "allauth", - "allauth.account", - "allauth.socialaccount", - # "allauth.socialaccount.providers.facebook", - "allauth_cas", -] - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - # admin needs it - "django.contrib.messages.middleware.MessageMiddleware", - # TODO: replace by CSP - # "django.middleware.clickjacking.XFrameOptionsMiddleware", - # 3rd party - "django_htmx.middleware.HtmxMiddleware", - # custom Fiesta - "apps.sections.middleware.user_membership.UserMembershipMiddleware", - "apps.plugins.middleware.plugin.CurrentPluginMiddleware", - "apps.accounts.middleware.user_profile.UserProfileMiddleware", -] - -ROOT_URLCONF = "fiesta.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [(BASE_DIR / "templates").as_posix()], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "fiesta.wsgi.application" - -# Database -# https://docs.djangoproject.com/en/4.0/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "HOST": "db", - "USER": "fiesta", - "NAME": "fiesta", - "PASSWORD": "fiesta", - }, - "legacydb": { - "ENGINE": "django.db.backends.mysql", - "HOST": "legacydb", - "NAME": "fiesta", - # TODO: access to legacy db by standard user - "USER": "root", - "PASSWORD": "root", - }, -} - -# Password validation -# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators - -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", - }, -] - -AUTH_USER_MODEL = "accounts.User" - -# TODO: check, which settings are needed & move to settings subpackage -AUTHENTICATION_BACKENDS = ( - "django.contrib.auth.backends.ModelBackend", - "allauth.account.auth_backends.AuthenticationBackend", -) - -PASSWORD_HASHERS = [ - "django.contrib.auth.hashers.PBKDF2PasswordHasher", - "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", - "django.contrib.auth.hashers.Argon2PasswordHasher", - "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", - "django.contrib.auth.hashers.ScryptPasswordHasher", - "apps.accounts.hashers.LegacyBCryptSHA256PasswordHasher", -] - -SITE_ID = 1 - -SECURE_PROXY_SSL_HEADER = "HTTP_X_FORWARDED_SSL", "on" - -SOCIALACCOUNT_PROVIDERS = { - "facebook": { - "METHOD": "oauth2", - "SDK_URL": "//connect.facebook.net/{locale}/sdk.js", - "SCOPE": ["email", "public_profile"], - # 'AUTH_PARAMS': {'auth_type': 'reauthenticate'}, - "INIT_PARAMS": {"cookie": True}, - "FIELDS": [ - "id", - "first_name", - "last_name", - "name", - "name_format", - "picture", - "short_name", - ], - "EXCHANGE_TOKEN": True, - "LOCALE_FUNC": lambda request: "en", - "VERIFIED_EMAIL": False, - "VERSION": "v12.0", - }, - "esnaccounts": {}, -} - -# configuration for fiesta accounts -ACCOUNT_AUTHENTICATION_METHOD = "username_email" # email or username -ACCOUNT_SESSION_REMEMBER = None # ask user for `remember` -ACCOUNT_ADAPTER = "apps.accounts.adapters.AccountAdapter" -ACCOUNT_EMAIL_REQUIRED = True # email ftw -ACCOUNT_USERNAME_REQUIRED = False # email ftw -ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" -# social account settings -SOCIALACCOUNT_AUTO_SIGNUP = True -SOCIALACCOUNT_ADAPTER = "apps.accounts.adapters.SocialAccountAdapter" -# general django urls -LOGIN_URL = "/auth/login" -LOGIN_REDIRECT_URL = "/" - -# fixme: verify it -ACCOUNT_LOGIN_ON_PASSWORD_RESET = True # False by default -ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True # True by default -ACCOUNT_USERNAME_MIN_LENGTH = 4 # a personal preference - -# Internationalization -# https://docs.djangoproject.com/en/4.0/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.0/howto/static-files/ - -STATIC_URL = "static/" - -STATIC_ROOT = config("STATIC_DIR", cast=Path) - -STATICFILES_DIRS = [ - (BASE_DIR / "static").as_posix(), - (BASE_DIR / "templates/static").as_posix(), -] - -MEDIA_ROOT = config("MEDIA_DIR", cast=Path) - -# internal for nginx -MEDIA_URL = "/media/" - -# Default primary key field type -# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -CSRF_TRUSTED_ORIGINS = ["https://*.localhost"] - -CSRF_COOKIE_SECURE = True -SESSION_COOKIE_SECURE = True -CSRF_COOKIE_HTTPONLY = True - -# DEBUG reasons -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - -WEBPACK_LOADER = { - "DEFAULT": { - "CACHE": False, - "BUNDLE_DIR_NAME": "./", # must end with slash - "STATS_FILE": config("BUILD_DIR", cast=Path) / "webpack-stats.json", - "INTEGRITY": not DEBUG, - } -} - -FORM_RENDERER = "django.forms.renderers.TemplatesSetting" - -if DEBUG: - INSTALLED_APPS.append("debug_toolbar") - INTERNAL_IPS = type("ContainsAll", (), {"__contains__": lambda *_: True})() - MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") - -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "verbose": { - "format": "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", - "datefmt": "%d/%b/%Y %H:%M:%S", - }, - "simple": {"format": "%(levelname)s %(message)s"}, - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "formatter": "verbose", - }, - }, - "root": { - "handlers": ["console"], - "level": "INFO", - }, - "loggers": { - "django": { - "handlers": ["console"], - "level": config("DJANGO_LOG_LEVEL", "INFO"), - "propagate": False, - }, - }, -} - -# DEBUG_PROPAGATE_EXCEPTIONS = False diff --git a/fiesta/fiesta/settings/__init__.py b/fiesta/fiesta/settings/__init__.py new file mode 100644 index 00000000..4466f122 --- /dev/null +++ b/fiesta/fiesta/settings/__init__.py @@ -0,0 +1,45 @@ +from configurations import Configuration + +from .auth import AuthConfigMixin +from .db import DatabaseConfigMixin +from .files import FilesConfigMixin +from .logging import LoggingConfigMixin +from .project import ProjectConfigMixin +from .security import SecurityConfigMixin +from .templates import TemplatesConfigMixin + + +class Base( + ProjectConfigMixin, + AuthConfigMixin, + DatabaseConfigMixin, + FilesConfigMixin, + LoggingConfigMixin, + SecurityConfigMixin, + TemplatesConfigMixin, + Configuration, +): + ... + + +class Development(Base): + DEBUG = True + DEBUG_PROPAGATE_EXCEPTIONS = False + + INTERNAL_IPS = type("ContainsAll", (), {"__contains__": lambda *_: True})() + + def INSTALLED_APPS(self): + return super().INSTALLED_APPS + ["debug_toolbar"] + + def MIDDLEWARE(self): + middlewares = super().MIDDLEWARE + middlewares.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") + return middlewares + + +class LocalProduction(Base): + DEBUG = False + + +class Production(Base): + DEBUG = False diff --git a/fiesta/fiesta/settings/_utils.py b/fiesta/fiesta/settings/_utils.py new file mode 100644 index 00000000..1ba6c25d --- /dev/null +++ b/fiesta/fiesta/settings/_utils.py @@ -0,0 +1,12 @@ +from pathlib import Path + +from configurations.values import CastingMixin, Value + + +class PathValue(CastingMixin, Value): + caster = Path + + +class BaseConfigurationProtocol: + DEBUG: bool + BASE_DIR: Path diff --git a/fiesta/fiesta/settings/auth.py b/fiesta/fiesta/settings/auth.py new file mode 100644 index 00000000..f3c3e958 --- /dev/null +++ b/fiesta/fiesta/settings/auth.py @@ -0,0 +1,75 @@ +class AuthConfigMixin: + 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", + }, + ] + + AUTH_USER_MODEL = "accounts.User" + + # TODO: check, which settings are needed + AUTHENTICATION_BACKENDS = ( + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", + ) + + PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + "django.contrib.auth.hashers.ScryptPasswordHasher", + "apps.accounts.hashers.LegacyBCryptSHA256PasswordHasher", + ] + + SOCIALACCOUNT_PROVIDERS = { + "facebook": { + "METHOD": "oauth2", + "SDK_URL": "//connect.facebook.net/{locale}/sdk.js", + "SCOPE": ["email", "public_profile"], + # 'AUTH_PARAMS': {'auth_type': 'reauthenticate'}, + "INIT_PARAMS": {"cookie": True}, + "FIELDS": [ + "id", + "first_name", + "last_name", + "name", + "name_format", + "picture", + "short_name", + ], + "EXCHANGE_TOKEN": True, + "LOCALE_FUNC": lambda request: "en", + "VERIFIED_EMAIL": False, + "VERSION": "v12.0", + }, + "esnaccounts": {}, + } + + # configuration for fiesta accounts + ACCOUNT_AUTHENTICATION_METHOD = "username_email" # email or username + ACCOUNT_SESSION_REMEMBER = None # ask user for `remember` + ACCOUNT_ADAPTER = "apps.accounts.adapters.AccountAdapter" + ACCOUNT_EMAIL_REQUIRED = True # email ftw + ACCOUNT_USERNAME_REQUIRED = False # email ftw + ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" + # social account settings + SOCIALACCOUNT_AUTO_SIGNUP = True + SOCIALACCOUNT_ADAPTER = "apps.accounts.adapters.SocialAccountAdapter" + # general django urls + LOGIN_URL = "/auth/login" + LOGIN_REDIRECT_URL = "/" + + # fixme: verify it + ACCOUNT_LOGIN_ON_PASSWORD_RESET = True # False by default + ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True # True by default + ACCOUNT_USERNAME_MIN_LENGTH = 4 # a personal preference diff --git a/fiesta/fiesta/settings/db.py b/fiesta/fiesta/settings/db.py new file mode 100644 index 00000000..808bf0ca --- /dev/null +++ b/fiesta/fiesta/settings/db.py @@ -0,0 +1,18 @@ +class DatabaseConfigMixin: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "HOST": "db", + "USER": "fiesta", + "NAME": "fiesta", + "PASSWORD": "fiesta", + }, + "legacydb": { + "ENGINE": "django.db.backends.mysql", + "HOST": "legacydb", + "NAME": "fiesta", + # TODO: access to legacy db by standard user + "USER": "root", + "PASSWORD": "root", + }, + } diff --git a/fiesta/fiesta/settings/files.py b/fiesta/fiesta/settings/files.py new file mode 100644 index 00000000..c7e4dd8b --- /dev/null +++ b/fiesta/fiesta/settings/files.py @@ -0,0 +1,32 @@ +from pathlib import Path + +from ._utils import PathValue, BaseConfigurationProtocol + + +class FilesConfigMixin(BaseConfigurationProtocol): + MEDIA_ROOT: Path = PathValue() + + STATIC_ROOT: Path = PathValue() + + BUILD_DIR: Path = PathValue() + + # internal for nginx + MEDIA_URL = "/media/" + STATIC_URL = "static/" + + def STATICFILES_DIRS(self): + return [ + (self.BASE_DIR / "static"), + (self.BASE_DIR / "templates/static"), + ] + + @property + def WEBPACK_LOADER(self): + return { + "DEFAULT": { + "CACHE": False, + "BUNDLE_DIR_NAME": "./", # must end with slash + "STATS_FILE": self.BUILD_DIR / "webpack-stats.json", + "INTEGRITY": not self.DEBUG, + } + } diff --git a/fiesta/fiesta/settings/logging.py b/fiesta/fiesta/settings/logging.py new file mode 100644 index 00000000..5d2f73a7 --- /dev/null +++ b/fiesta/fiesta/settings/logging.py @@ -0,0 +1,35 @@ +from configurations.values import Value + + +class LoggingConfigMixin: + LOG_LEVEL: str = Value("INFO") + + def LOGGING(self): + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", + "datefmt": "%d/%b/%Y %H:%M:%S", + }, + "simple": {"format": "%(levelname)s %(message)s"}, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + }, + "root": { + "handlers": ["console"], + "level": "INFO", + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": self.LOG_LEVEL, + "propagate": False, + }, + }, + } diff --git a/fiesta/fiesta/settings/project.py b/fiesta/fiesta/settings/project.py new file mode 100644 index 00000000..84263756 --- /dev/null +++ b/fiesta/fiesta/settings/project.py @@ -0,0 +1,81 @@ +from pathlib import Path + +from ._utils import PathValue + + +class ProjectConfigMixin: + BASE_DIR = PathValue(Path(__file__).resolve().parent.parent.parent) + + DEBUG = False + + DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + SITE_ID = 1 + LANGUAGE_CODE = "en-us" + + TIME_ZONE = "UTC" + + USE_I18N = True + + USE_TZ = True + + ALLOWED_HOSTS: list[str] = [".localhost", "127.0.0.1"] + + WSGI_APPLICATION = "fiesta.wsgi.application" + + ROOT_URLCONF = "fiesta.urls" + + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + + INSTALLED_APPS = [ + # Django native + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "django.contrib.humanize", + "django.forms", + # Django 3rd party + "polymorphic", + "webpack_loader", + "django_htmx", + # Fiesta apps + "apps.accounts.apps.AccountsConfig", + "apps.esnaccounts", # cannot have full config Path, since allauth/socialaccount/providers/__init__.py:38 sucks + "apps.esncards.apps.ESNcardsConfig", + "apps.fiestaforms.apps.FiestaformsConfig", + "apps.plugins.apps.PluginsConfig", + "apps.sections.apps.SectionsConfig", + "apps.universities.apps.UniversitiesConfig", + "apps.utils.apps.UtilsConfig", + "apps.wiki.apps.WikiConfig", + # Debugs + "django_extensions", + # django-allauth + "allauth", + "allauth.account", + "allauth.socialaccount", + # "allauth.socialaccount.providers.facebook", + "allauth_cas", + ] + + MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + # admin needs it + "django.contrib.messages.middleware.MessageMiddleware", + # TODO: replace by CSP + # "django.middleware.clickjacking.XFrameOptionsMiddleware", + # 3rd party + "django_htmx.middleware.HtmxMiddleware", + # custom Fiesta + "apps.sections.middleware.user_membership.UserMembershipMiddleware", + "apps.plugins.middleware.plugin.CurrentPluginMiddleware", + "apps.accounts.middleware.user_profile.UserProfileMiddleware", + ] diff --git a/fiesta/fiesta/settings/security.py b/fiesta/fiesta/settings/security.py new file mode 100644 index 00000000..3250e1f5 --- /dev/null +++ b/fiesta/fiesta/settings/security.py @@ -0,0 +1,12 @@ +from configurations.values import SecretValue + + +class SecurityConfigMixin: + SECRET_KEY = SecretValue() + + SECURE_PROXY_SSL_HEADER = "HTTP_X_FORWARDED_SSL", "on" + CSRF_TRUSTED_ORIGINS = ["https://*.localhost"] + + CSRF_COOKIE_SECURE = True + SESSION_COOKIE_SECURE = True + CSRF_COOKIE_HTTPONLY = True diff --git a/fiesta/fiesta/settings/templates.py b/fiesta/fiesta/settings/templates.py new file mode 100644 index 00000000..2f8e3d29 --- /dev/null +++ b/fiesta/fiesta/settings/templates.py @@ -0,0 +1,22 @@ +from ._utils import BaseConfigurationProtocol + + +class TemplatesConfigMixin(BaseConfigurationProtocol): + FORM_RENDERER = "django.forms.renderers.TemplatesSetting" + + def TEMPLATES(self): + return [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [self.BASE_DIR / "templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, + ] diff --git a/fiesta/manage.py b/fiesta/manage.py index 2008ee93..9d9092db 100755 --- a/fiesta/manage.py +++ b/fiesta/manage.py @@ -3,12 +3,11 @@ import os import sys -from django.core.management import execute_from_command_line - def main(): - """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fiesta.settings") + os.environ.setdefault("DJANGO_CONFIGURATION", "Development") + from configurations.management import execute_from_command_line execute_from_command_line(sys.argv) diff --git a/fiesta/run.sh b/fiesta/run.sh index 124f054b..78003fdc 100755 --- a/fiesta/run.sh +++ b/fiesta/run.sh @@ -1,4 +1,4 @@ #!/bin/bash -chown 1000:1000 -R /usr/src/ && chmod a+wx -R /usr/src/media +chown 1000:1000 -R /usr/src/ && chmod a+wx -R /usr/src/media && chmod a+wx -R /usr/src/static exec su 1000 -c "$*" diff --git a/poetry.lock b/poetry.lock index 55115918..00c6a4d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -117,6 +117,33 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "dj-database-url" +version = "0.5.0" +description = "Use Database URLs in your Django Application." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "dj-email-url" +version = "1.0.5" +description = "Use an URL to configure email backend settings in your Django Application." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "dj-search-url" +version = "0.1" +description = "Use Search URLs in your Django Haystack Application." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +tests = ["pytest"] + [[package]] name = "django" version = "4.0.2" @@ -173,6 +200,14 @@ url = "https://github.com/thejoeejoee/django-allauth-cas.git" reference = "master" resolved_reference = "5db34b546eb32524a3a1a4b90f411e370ac7ad9b" +[[package]] +name = "django-cache-url" +version = "3.3.0" +description = "Use Cache URLs in your Django application." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "django-click" version = "2.3.0" @@ -188,6 +223,28 @@ click = ">=7.1" dev = ["fabric", "livereload", "wheel", "check-manifest", "flake8", "mccabe", "pep8", "pep8-naming", "pyflakes", "sphinx", "sphinx-autobuild", "sphinx-rtd-theme"] test = ["pytest", "coverage (<5)", "pytest-django", "pytest-cov", "pytest-flake8"] +[[package]] +name = "django-configurations" +version = "2.3.2" +description = "A helper for organizing Django settings." +category = "main" +optional = false +python-versions = ">=3.6, <4.0" + +[package.dependencies] +dj-database-url = {version = "*", optional = true, markers = "extra == \"database\""} +dj-email-url = {version = "*", optional = true, markers = "extra == \"email\""} +dj-search-url = {version = "*", optional = true, markers = "extra == \"search\""} +django = ">=2.2" +django-cache-url = {version = "*", optional = true, markers = "extra == \"cache\""} + +[package.extras] +cache = ["django-cache-url"] +database = ["dj-database-url"] +email = ["dj-email-url"] +search = ["dj-search-url"] +testing = ["django-cache-url (>=1.0.0)", "dj-database-url", "dj-email-url", "dj-search-url"] + [[package]] name = "django-countries" version = "7.2.1" @@ -482,14 +539,6 @@ lxml = ">=3.4" requests = ">=2.11.1" six = ">=1.10.0" -[[package]] -name = "python-decouple" -version = "3.6" -description = "Strict separation of settings from code." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "python-magic" version = "0.4.25" @@ -628,7 +677,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "6ed971c50d22d4c35f4564c19cae84a2b15beb6ed98e30ad3a6a786e941eb926" +content-hash = "9edd33862b2ba776bb1fe62b6a148b965e3bcb8899f674a9e6669a298311ac74" [metadata.files] asgiref = [ @@ -749,6 +798,17 @@ distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] +dj-database-url = [ + {file = "dj-database-url-0.5.0.tar.gz", hash = "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163"}, + {file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"}, +] +dj-email-url = [ + {file = "dj-email-url-1.0.5.tar.gz", hash = "sha256:ef36f8a324ec57cf3be5c7a7ef44ed6900ca0208624a918ab33adc1cf6427b39"}, + {file = "dj_email_url-1.0.5-py2.py3-none-any.whl", hash = "sha256:64257c4f9d8139a4af8e5267229d32260e433fbf257b0cf8fc855bb0cc39ca7d"}, +] +dj-search-url = [ + {file = "dj-search-url-0.1.tar.gz", hash = "sha256:424d1a5852500b3c118abfdd0e30b3e0016fe68e7ed27b8553a67afa20d4fb40"}, +] django = [ {file = "Django-4.0.2-py3-none-any.whl", hash = "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a"}, {file = "Django-4.0.2.tar.gz", hash = "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a"}, @@ -757,10 +817,18 @@ django-allauth = [ {file = "django-allauth-0.47.0.tar.gz", hash = "sha256:2bcf09d4c6e672620981d283f555d643982ac066b71f8947dbd81882a1d7c5e8"}, ] django-allauth-cas = [] +django-cache-url = [ + {file = "django-cache-url-3.3.0.tar.gz", hash = "sha256:888c6954fdb3c7223e771b61528d7f7dc5987965cd93a6881b9bb31f0ef6d394"}, + {file = "django_cache_url-3.3.0-py2.py3-none-any.whl", hash = "sha256:dd689515d78a824f469d9cd4e447198999d2f13d1279487da20203f2f3b1e06b"}, +] django-click = [ {file = "django-click-2.3.0.tar.gz", hash = "sha256:bffb0d10c3f3dfe3f5b116ce902c7a7a9aa4e9c046de95259dc044462eb746b4"}, {file = "django_click-2.3.0-py2.py3-none-any.whl", hash = "sha256:b862d6b2424edba564968834c8570e39db8142babaa843fa791a4958a2dabb54"}, ] +django-configurations = [ + {file = "django-configurations-2.3.2.tar.gz", hash = "sha256:bd1a77a60735839b7d105912cc3977735fa005ea06544c632fbd322d1e021677"}, + {file = "django_configurations-2.3.2-py3-none-any.whl", hash = "sha256:c0363b0111df6536442186d0f23d12138390eaa9f4f97ce46950ee78856a46c9"}, +] django-countries = [ {file = "django-countries-7.2.1.tar.gz", hash = "sha256:26878b54d36bedff30b4535ceefcb8af6784741a8b30b1b8a662fb14a936a4ab"}, {file = "django_countries-7.2.1-py3-none-any.whl", hash = "sha256:adc965f1d348124274b7d918fc1aad5e29609758af999e1822baa9f2cc06d1b8"}, @@ -1004,10 +1072,6 @@ python-cas = [ {file = "python-cas-1.6.0.tar.gz", hash = "sha256:b8f1dcb1b6c56b3ff4f86bbef47bcdfcf932061ccd4812ae35e3f63954dfdb28"}, {file = "python_cas-1.6.0-py2.py3-none-any.whl", hash = "sha256:2abc0dae93c3b14097999fb7062f23cd09bc9f4e33d93f03c0cc040bd71ed50e"}, ] -python-decouple = [ - {file = "python-decouple-3.6.tar.gz", hash = "sha256:2838cdf77a5cf127d7e8b339ce14c25bceb3af3e674e039d4901ba16359968c7"}, - {file = "python_decouple-3.6-py3-none-any.whl", hash = "sha256:6cf502dc963a5c642ea5ead069847df3d916a6420cad5599185de6bab11d8c2e"}, -] python-magic = [ {file = "python-magic-0.4.25.tar.gz", hash = "sha256:21f5f542aa0330f5c8a64442528542f6215c8e18d2466b399b0d9d39356d83fc"}, {file = "python_magic-0.4.25-py2.py3-none-any.whl", hash = "sha256:1a2c81e8f395c744536369790bd75094665e9644110a6623bcc3bbea30f03973"}, diff --git a/pyproject.toml b/pyproject.toml index 8ea2ab54..420c4e07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ authors = ["ESN VUT BRNO"] [tool.poetry.dependencies] python = "^3.9" django = "^4.0.1" -python-decouple = "^3.5" psycopg2-binary = "^2.9.3" django-debug-toolbar = "^3.2.4" django-polymorphic = "^3.1.0" @@ -24,6 +23,7 @@ django-allauth-cas = { git = "https://github.com/thejoeejoee/django-allauth-cas. elasticsearch = "^7.17.0" Pillow = "^9.0.1" python-magic = "^0.4.25" +django-configurations = {extras = ["cache", "database", "email", "search"], version = "^2.3.2"} [tool.poetry.dev-dependencies] pre-commit = "^2.17.0"