diff --git a/Dockerfile b/Dockerfile index 709062a25582343a3e49aad857d7b3a9f3427edd..65237693e2fdb9f3ec87020516de6d29ab633b2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,13 +16,18 @@ COPY . . RUN bash -c 'adduser --disabled-login --quiet --gecos app app && \ chmod -R o+r /app/ && \ mkdir /app/media_files && \ + mkdir /app/static_files && \ chown -R app:app /app/media_files && \ + chown -R app:app /app/static_files && \ chmod o+x /app/run.sh' USER app -# TODO HACK! -# ENV DJANGO_SETTINGS_MODULE "majak.settings.production" -ENV DJANGO_SETTINGS_MODULE "majak.settings.dev" +ENV DJANGO_SETTINGS_MODULE "majak.settings.production" + +# fake values for required env variables used to run collectstatic during build +RUN DJANGO_SECRET_KEY=x DATABASE_URL=postgres://x/x DJANGO_ALLOWED_HOSTS=x \ + OIDC_RP_CLIENT_ID=x OIDC_RP_CLIENT_SECRET=x OIDC_RP_REALM_URL=x \ + python manage.py collectstatic EXPOSE 8000 diff --git a/README.md b/README.md index 1b8b37b47711f3e9a6f11ce5ff89dd8d2c5ef729..549f58ddb4dbcadc4dd17c16a2196cfb83be5f0e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,24 @@ Maják je CMS pro PirátskĂ© weby. PostavenĂ˝ je na [Wagtail](https://wagtail.io [](https://wagtail.io) [](https://www.djangoproject.com) -## Konfigurace +## Pod pokliÄŤkou + +[Wagtail](https://wagtail.io) a [Django](https://www.djangoproject.com) jsou pomÄ›rnÄ› +vyspÄ›lĂ© frameworky. VĹľdy mysli na to, Ĺľe problĂ©m co Ĺ™ešĂš, uĹľ pravdÄ›podobnÄ› Ĺ™ešil +nÄ›kdo pĹ™ed tebou. A obvykle existuje elegantnĂ Ĺ™ešenĂ. + +Pár uĹľiteÄŤnĂ˝ch odkazĹŻ: + +* [docs Wagtail](https://docs.wagtail.io/) +* [docs Django](https://docs.djangoproject.com/) +* [docs Wagtailmenus](https://wagtailmenus.readthedocs.io/) + +A za zmĂnku stojĂ [Awesome Wagtail](https://github.com/springload/awesome-wagtail) +jako pĹ™ehled pluginĹŻ a rozšĂĹ™enĂ pro Wagtail. + +## Deployment + +### Konfigurace Je tĹ™eba nastavit environment promÄ›nnĂ©: @@ -14,29 +31,24 @@ Je tĹ™eba nastavit environment promÄ›nnĂ©: | --- | --- | --- | | `DATABASE_URL` | | DSN k databázi (napĹ™. `postgres://user:pass@localhost:5342/majak`) | | `OIDC_RP_REALM_URL` | | OpenID server realm URL (napĹ™. `http://localhost:8080/auth/realms/master/`) | -| `OIDC_RP_CLIENT_ID` | | OpenID Client ID | -| `OIDC_RP_CLIENT_SECRET` | | OpenID Client Secret | +| `OIDC_RP_CLIENT_ID` | | OpenID Client ID | +| `OIDC_RP_CLIENT_SECRET` | | OpenID Client Secret | +| `BASE_URL` | https://majak.pirati.cz | základnĂ URL pro notifikaÄŤnĂ emaily apod. | V produkci musĂ bĂ˝t navĂc nastaveno: | promÄ›nná | default | popis | | --- | --- | --- | | `DJANGO_SECRET_KEY` | | tajnĂ˝ šifrovacĂ klĂÄŤ | +| `DJANGO_ALLOWED_HOSTS` | | allowed hosts (vĂce hodnot oddÄ›leno čárkami) | -## Pod pokliÄŤkou - -[Wagtail](https://wagtail.io) a [Django](https://www.djangoproject.com) jsou pomÄ›rnÄ› -vyspÄ›lĂ© frameworky. VĹľdy mysli na to, Ĺľe problĂ©m co Ĺ™ešĂš, uĹľ pravdÄ›podobnÄ› Ĺ™ešil -nÄ›kdo pĹ™ed tebou. A obvykle existuje elegantnĂ Ĺ™ešenĂ. - -Pár uĹľiteÄŤnĂ˝ch odkazĹŻ: +### PĹ™idánĂ novĂ©ho webu -* [docs Wagtail](https://docs.wagtail.io/) -* [docs Django](https://docs.djangoproject.com/) -* [docs Wagtailmenus](https://wagtailmenus.readthedocs.io/) +DomĂ©na ÄŤi subdomĂ©na se musĂ nakonfigurovat v: -A za zmĂnku stojĂ [Awesome Wagtail](https://github.com/springload/awesome-wagtail) -jako pĹ™ehled pluginĹŻ a rozšĂĹ™enĂ pro Wagtail. +* environment promÄ›nnĂ© `DJANGO_ALLOWED_HOSTS` +* proxy pĹ™ed Majákem +* SSO Client redirect URIs ## VĂ˝voj diff --git a/majak/settings/base.py b/majak/settings/base.py index cc6c4ccbccd5649d2fb1c2f1b40108a25b31f39a..3f546ac79ee3038af780223b8e305062a1f0dc2d 100644 --- a/majak/settings/base.py +++ b/majak/settings/base.py @@ -9,8 +9,27 @@ PROJECT_DIR = ROOT_DIR / "majak" env = environ.Env() environ.Env.read_env(str(ROOT_DIR / ".env")) -# Application definition +# GENERAL +# ------------------------------------------------------------------------------ +DEBUG = env.bool("DJANGO_DEBUG", False) +ROOT_URLCONF = "majak.urls" +WSGI_APPLICATION = "majak.wsgi.application" +# I18N and L10N +# ------------------------------------------------------------------------------ +TIME_ZONE = "Europe/Prague" +LANGUAGE_CODE = "cs" +USE_I18N = True +USE_L10N = True +USE_TZ = True + +# DATABASES +# ------------------------------------------------------------------------------ +DATABASES = {"default": env.db("DATABASE_URL")} +DATABASES["default"]["ATOMIC_REQUESTS"] = True + +# APPS +# ------------------------------------------------------------------------------ INSTALLED_APPS = [ "search", "senator", @@ -41,6 +60,25 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", ] +# AUTHENTICATION +# ------------------------------------------------------------------------------ +AUTHENTICATION_BACKENDS = ["pirates.auth.PiratesOIDCAuthenticationBackend"] +AUTH_USER_MODEL = "users.User" +LOGIN_REDIRECT_URL = "/admin" +LOGOUT_REDIRECT_URL = "/admin" +LOGIN_URL = "/admin" + +OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID") +OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET") +OIDC_RP_REALM_URL = env.str("OIDC_RP_REALM_URL") +OIDC_RP_SIGN_ALGO = "RS256" +OIDC_OP_JWKS_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/certs") +OIDC_OP_AUTHORIZATION_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/auth") +OIDC_OP_TOKEN_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/token") +OIDC_OP_USER_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/userinfo") + +# MIDDLEWARE +# ------------------------------------------------------------------------------ MIDDLEWARE = [ "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -52,14 +90,33 @@ MIDDLEWARE = [ "wagtail.contrib.redirects.middleware.RedirectMiddleware", ] -ROOT_URLCONF = "majak.urls" +# STATIC +# ------------------------------------------------------------------------------ +STATIC_ROOT = str(ROOT_DIR / "static_files") +STATIC_URL = "/static/" +STATICFILES_DIRS = [str(PROJECT_DIR / "static")] +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] +STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" + +# MEDIA +# ------------------------------------------------------------------------------ +MEDIA_URL = "/media/" +MEDIA_ROOT = str(ROOT_DIR / "media_files") +# TEMPLATES +# ------------------------------------------------------------------------------ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [str(PROJECT_DIR / "templates")], - "APP_DIRS": True, "OPTIONS": { + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ], "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", @@ -72,75 +129,52 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = "majak.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/3.0/ref/settings/#databases - -DATABASES = {"default": env.db("DATABASE_URL")} -DATABASES["default"]["ATOMIC_REQUESTS"] = True - - -# Password validation -# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", +# SECURITY +# ------------------------------------------------------------------------------ +SESSION_COOKIE_HTTPONLY = True +CSRF_COOKIE_HTTPONLY = True +SECURE_BROWSER_XSS_FILTER = True +X_FRAME_OPTIONS = "DENY" + +# EMAIL +# ------------------------------------------------------------------------------ +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.dummy.EmailBackend" +) + +# LOGGING +# ------------------------------------------------------------------------------ +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s" + } }, - {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, -] - - -# Internationalization -# https://docs.djangoproject.com/en/3.0/topics/i18n/ - -LANGUAGE_CODE = "cs" -TIME_ZONE = "Europe/Prague" -USE_I18N = True -USE_L10N = True -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.0/howto/static-files/ - -STATICFILES_FINDERS = [ - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", -] - -STATICFILES_DIRS = [str(PROJECT_DIR / "static")] - -# ManifestStaticFilesStorage is recommended in production, to prevent outdated -# Javascript / CSS assets being served from cache (e.g. after a Wagtail upgrade). -# See https://docs.djangoproject.com/en/3.0/ref/contrib/staticfiles/#manifeststaticfilesstorage -STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" - -STATIC_ROOT = str(ROOT_DIR / "static_files") -STATIC_URL = "/static/" - -MEDIA_ROOT = str(ROOT_DIR / "media_files") -MEDIA_URL = "/media/" - - -# Wagtail settings + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, +} +# WAGTAIL SETTINGS +# ------------------------------------------------------------------------------ WAGTAIL_SITE_NAME = "Maják" -# Base URL to use when referring to full URLs within the Wagtail admin backend - -# e.g. in notification emails. Don't include '/admin' or a trailing slash -BASE_URL = "http://example.com" - WAGTAIL_ALLOW_UNICODE_SLUGS = False TAGGIT_CASE_INSENSITIVE = True -AUTH_USER_MODEL = "users.User" +WAGTAIL_USER_TIME_ZONES = ["Europe/Prague"] + +WAGTAILADMIN_NOTIFICATION_INCLUDE_SUPERUSERS = False +# disable editing of user details synced from SSO WAGTAIL_USER_EDIT_FORM = "users.forms.CustomUserEditForm" WAGTAIL_USER_CREATION_FORM = "users.forms.CustomUserCreationForm" WAGTAIL_PASSWORD_MANAGEMENT_ENABLED = False @@ -149,17 +183,6 @@ WAGTAILUSERS_PASSWORD_ENABLED = False WAGTAILUSERS_PASSWORD_REQUIRED = False WAGTAIL_EMAIL_MANAGEMENT_ENABLED = False -AUTHENTICATION_BACKENDS = ["pirates.auth.PiratesOIDCAuthenticationBackend"] - -OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID") -OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET") -OIDC_RP_REALM_URL = env.str("OIDC_RP_REALM_URL") -OIDC_RP_SIGN_ALGO = "RS256" -OIDC_OP_JWKS_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/certs") -OIDC_OP_AUTHORIZATION_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/auth") -OIDC_OP_TOKEN_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/token") -OIDC_OP_USER_ENDPOINT = join(OIDC_RP_REALM_URL, "protocol/openid-connect/userinfo") - -LOGIN_REDIRECT_URL = "/admin" -LOGOUT_REDIRECT_URL = "/admin" -LOGIN_URL = "/admin" +# Base URL to use when referring to full URLs within the Wagtail admin backend - +# e.g. in notification emails. Don't include '/admin' or a trailing slash +BASE_URL = env.str("BASE_URL", default="https://majak.pirati.cz") diff --git a/majak/settings/dev.py b/majak/settings/dev.py index 3ae9e0c6ff57fae8b2ba31d32fcba9f2bae2dd90..14ef7e2d42b5e9a8a17a4799affb3f096f55b258 100644 --- a/majak/settings/dev.py +++ b/majak/settings/dev.py @@ -1,18 +1,8 @@ from .base import * +from .base import env -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "1vu8ve^2vdu%k00s7&)l+^a@b5aht53el96evte(7d)0@!dyk1" - -# SECURITY WARNING: define the correct hosts in production! -ALLOWED_HOSTS = ["*"] - -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - - -try: - from .local import * -except ImportError: - pass +# GENERAL +# ------------------------------------------------------------------------------ +DEBUG = env.bool("DJANGO_DEBUG", default=True) +SECRET_KEY = env("DJANGO_SECRET_KEY", default="58asda4d6nasd*jkj!dbska83asd54") +ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["*"]) diff --git a/majak/settings/production.py b/majak/settings/production.py index 9ca4ed75279d4b38ece0feb0254929ed934033e4..2d7e44b39c35ff7e47c79cb95fae9a992ac31650 100644 --- a/majak/settings/production.py +++ b/majak/settings/production.py @@ -1,8 +1,43 @@ from .base import * +from .base import env -DEBUG = False +# DATABASES +# ------------------------------------------------------------------------------ +DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) -try: - from .local import * -except ImportError: - pass +# SECURITY +# ------------------------------------------------------------------------------ +ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS") +SECRET_KEY = env("DJANGO_SECRET_KEY") +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +SECURE_SSL_REDIRECT = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +# set this to 60 seconds first and then to 518400 once you prove the former works +SECURE_HSTS_SECONDS = 518400 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True +SECURE_CONTENT_TYPE_NOSNIFF = True + +# TEMPLATES +# ------------------------------------------------------------------------------ +TEMPLATES[0]["OPTIONS"]["loaders"] = [ + ( + "django.template.loaders.cached.Loader", + [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ], + ) +] + +# STATIC +# ------------------------------------------------------------------------------ +MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware") +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" + +# LOGGING +# ------------------------------------------------------------------------------ +LOGGING["filters"] = { + "require_debug_false": {"()": "django.utils.log.RequireDebugFalse"} +} diff --git a/requirements/base.in b/requirements/base.in index 4b01ce6933ad6a1a9d1614e90d6bdbb9e717d9f2..29e83430e56bb22ae22bb4aeacb084104d087596 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -4,3 +4,4 @@ django-environ django-extensions psycopg2-binary pirates<=0.4 +whitenoise diff --git a/requirements/base.txt b/requirements/base.txt index 8f2ba5608af2333b522a00815550b223427c8a77..80fdc30acb14801820c46925a717cdc10756188e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -42,6 +42,7 @@ urllib3==1.25.9 # via requests wagtail==2.9 # via -r base.in wagtailmenus==3.0.1 # via -r base.in webencodings==0.5.1 # via html5lib +whitenoise==5.0.1 # via -r base.in willow==1.3 # via wagtail xlsxwriter==1.2.8 # via wagtail