Source code for cobalt.settings

"""Cobalt Settings.py

This is the single settings.py for all Cobalt environments.

We manage all configuration differences through environment variables.
This provides security for confidential information in the online
environments (Test, UAT and Production) which is managed by Elastic
Beanstalk through settings which become environment at run-time.

For development, you also need to set environment variables, or it
won't work.

"""

import os
import ast
from django.contrib.messages import constants as messages
from firebase_admin import initialize_app


###########################################
# function to set values from environment #
# variables.                              #
###########################################
[docs] def set_value(val_name, default="not-set"): return os.environ[val_name] if val_name in os.environ else default
[docs] def apply_large_email_batch_config(batch_size): """ Checks whether a large email batch configuration set is both configured and applicable to this batch size. """ if AWS_SES_CONFIGURATION_SET_LARGE is None: return False return batch_size >= EMAIL_LARGE_BATCH_SIZE
[docs] def AWS_SES_configuration_set_selector( email_message, dkim_domain=None, dkim_key=None, dkim_selector=None, dkim_headers=() ): """ Selects the appropriate Amazon Simple Email System configuration set for an email, based on batch size (optionally passed in the email via a custom header X-Myabf-Batch-Size). This function is called by the Django-SES package (specified by AWS_SES_CONFIGURATION_SET), and only when AWS_SES_CONFIGURATION_SET_DEFAULT is set. See https://github.com/django-ses/django-ses#ses-event-monitoring-with-configuration-sets and COB-793 for more details. """ if ( AWS_SES_CONFIGURATION_SET_LARGE is None or "X-Myabf-Batch-Size" not in email_message.extra_headers ): return AWS_SES_CONFIGURATION_SET_DEFAULT batch_size = int(email_message.extra_headers["X-Myabf-Batch-Size"]) if apply_large_email_batch_config(batch_size): return AWS_SES_CONFIGURATION_SET_LARGE else: return AWS_SES_CONFIGURATION_SET_DEFAULT
########################################### # base settings that need to come first. # ########################################### BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ########################################### # Specific settings per environment. # # Override through environment variables. # # Dummy values are required for read the # # docs to work. # ########################################### # basics SECRET_KEY = set_value("SECRET_KEY") DEBUG = os.environ.get("DEBUG") == "ON" API_KEY_PREFIX = set_value("API_KEY_PREFIX", "API_") # Set up ADMINS list from string ADMINS = [ ("Mark Guthrie", "m@rkguthrie.com"), # ("Julian Foster", "Julian.Foster@abf.com.au"), ] # COB-488 - require 2FA for Admin site access (Y or N) REQUIRE_2FA = set_value("REQUIRE_2FA", "N") == "Y" SERVER_EMAIL = set_value("SERVER_EMAIL", "notset@myabf.com.au") # masterpoints server GLOBAL_MPSERVER = set_value("GLOBAL_MPSERVER") # email EMAIL_HOST = set_value("EMAIL_HOST") EMAIL_HOST_USER = set_value("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = set_value("EMAIL_HOST_PASSWORD") DEFAULT_FROM_EMAIL = set_value("DEFAULT_FROM_EMAIL", "notset@fake.com") # TODO: SUPPORT_EMAIL is only used to send client side errors - replace with ADMINS SUPPORT_EMAIL = set_value("SUPPORT_EMAIL", ["success@simulator.amazonses.com"]) # playpen - don't send emails from non-prod systems DISABLE_PLAYPEN = set_value("DISABLE_PLAYPEN", "OFF") # stripe STRIPE_SECRET_KEY = set_value("STRIPE_SECRET_KEY") STRIPE_PUBLISHABLE_KEY = set_value("STRIPE_PUBLISHABLE_KEY") # aws AWS_ACCESS_KEY_ID = set_value("AWS_ACCESS_KEY_ID") AWS_SECRET_ACCESS_KEY = set_value("AWS_SECRET_ACCESS_KEY") AWS_REGION_NAME = set_value("AWS_REGION_NAME") AWS_SES_REGION_NAME = AWS_REGION_NAME AWS_SES_REGION_ENDPOINT = set_value("AWS_SES_REGION_ENDPOINT") # See COB-793: changes to SES configuration set handling # Selector function returns the appropriate configuration set for an email # Either AWS_SES_CONFIGURATION_SET_DEFAULT or AWS_SES_CONFIGURATION_SET must be set EMAIL_LARGE_BATCH_SIZE = int(set_value("EMAIL_LARGE_BATCH_SIZE", 100)) AWS_SES_CONFIGURATION_SET_DEFAULT = set_value("AWS_SES_CONFIGURATION_SET_DEFAULT", None) AWS_SES_CONFIGURATION_SET_LARGE = set_value("AWS_SES_CONFIGURATION_SET_LARGE", None) if AWS_SES_CONFIGURATION_SET_DEFAULT is None: AWS_SES_CONFIGURATION_SET = set_value("AWS_SES_CONFIGURATION_SET") else: AWS_SES_CONFIGURATION_SET = AWS_SES_configuration_set_selector # Set this to false so we don't need to install m2crypto which needs OS installs to work # Not verifying the certificate is lower risk than having us rely on an OS install AWS_SES_VERIFY_EVENT_SIGNATURES = False # our logical hostname (dev, test, uat, prod) COBALT_HOSTNAME = set_value("COBALT_HOSTNAME", "127.0.0.1:8000") # Hostname set by AWS HOSTNAME = set_value("HOSTNAME", "Unknown") # New Relic App ID - Test, UAT and Prod all have their own IDs. Default to Dev NEW_RELIC_APP_ID = set_value("NEW_RELIC_APP_ID", "601323710") # Masterpoint source MP_USE_FILE = set_value("MP_USE_FILE", None) # database RDS_DB_NAME = set_value("RDS_DB_NAME") RDS_USERNAME = set_value("RDS_USERNAME") RDS_PASSWORD = set_value("RDS_PASSWORD") RDS_HOSTNAME = set_value("RDS_HOSTNAME") RDS_PORT = set_value("RDS_PORT") USE_SQLITE = set_value("USE_SQLITE", 0) # xero XERO_CLIENT_ID = set_value("XERO_CLIENT_ID") XERO_CLIENT_SECRET = set_value("XERO_CLIENT_SECRET") XERO_TENANT_NAME = "17 Ways" DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": RDS_DB_NAME, "USER": RDS_USERNAME, "PASSWORD": RDS_PASSWORD, "HOST": RDS_HOSTNAME, "PORT": RDS_PORT, } } # Test Only - Dummy data count DUMMY_DATA_COUNT = int(set_value("DUMMY_DATA_COUNT", 20)) # Maintenance mode setting used by cobalt.middleware # Set this to the string "ON" to put site into maintenance mode - only admins can login MAINTENANCE_MODE = set_value("MAINTENANCE_MODE", "OFF") # Recaptcha keys RECAPTCHA_SITE_KEY = set_value("RECAPTCHA_SITE_KEY") RECAPTCHA_SECRET_KEY = set_value("RECAPTCHA_SECRET_KEY") ######################################### # Dynamic settings. # ######################################### ALLOWED_HOSTS = [ "myabf.com.au", ".myabf.com.au", "127.0.0.1", "bs-local.com", "localhost", "testserver", ".eba-4ngvp62w.ap-southeast-2.elasticbeanstalk.com", ] # In development, allow any connections if COBALT_HOSTNAME == "127.0.0.1:8000": ALLOWED_HOSTS = ["*"] # For AWS we also need to add the local IP address as this is used by the health checks # We do this dynamically # Windows doesn't support this and isn't used on AWS so skip unless Unix if os.name == "posix": local_ip = os.popen("hostname -I 2>/dev/null").read() ALLOWED_HOSTS.append(local_ip.strip()) ######################################### # Common settings for all environments # ######################################### INSTALLED_APPS = [ "calendar_app", "api", "notifications", "events", "forums", "masterpoints", "payments", "support", "accounts", "dashboard", "results", "organisations", "logs", "rbac", "cobalt", "club_sessions", "utils", "tests", "xero", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.humanize", "django_summernote", "crispy_forms", "crispy_bootstrap4", "widget_tweaks", "django_extensions", "django.contrib.admindocs", "post_office", "django_ses", "django_otp", "django_otp.plugins.otp_totp", "loginas", "fcm_django", ] MIDDLEWARE = [ "utils.middleware.CobaltMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "cobalt.middleware.MaintenanceModeMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "django_otp.middleware.OTPMiddleware", ] ROOT_URLCONF = "cobalt.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": ["cobalt/templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "cobalt.context_processors.global_settings", "loginas.context_processors.impersonated_session_status", ], }, }, { "BACKEND": "post_office.template.backends.post_office.PostOfficeTemplates", "APP_DIRS": True, "DIRS": [], "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", "django.template.context_processors.request", ] }, }, ] # We use django-loginas to allow admins to take over sessions CAN_LOGIN_AS = "utils.can_login_as.check" CRISPY_TEMPLATE_PACK = "bootstrap4" WSGI_APPLICATION = "cobalt.wsgi.application" AUTH_USER_MODEL = "accounts.User" AUTHENTICATION_BACKENDS = ["accounts.backend.CobaltBackend"] 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"}, ] EMAIL_BACKEND = "post_office.EmailBackend" POST_OFFICE = { "BACKENDS": { "default": "django_ses.SESBackend", }, "TEMPLATE_ENGINE": "post_office", "DEFAULT_PRIORITY": "medium", "MESSAGE_ID_ENABLED": True, } EMAIL_USE_TLS = True EMAIL_PORT = 587 LANGUAGE_CODE = "en-au" TIME_ZONE = "Australia/Sydney" USE_I18N = True USE_L10N = True USE_TZ = True DATE_FORMAT = "j M Y" TIME_FORMAT = "g:I A" DATE_INPUT_FORMATS = ["%d %b %Y", "%d/%m/%Y", "%d %b %Y"] TIME_INPUT_FORMATS = [ "%I:%M %p", ] # This is where collectstatic will put the static files it finds STATIC_ROOT = os.path.join(BASE_DIR, "static") # External reference point to find static STATIC_URL = "/static/" # append MD5 hash to filenames to prevent caching on version change STATICFILES_STORAGE = "utils.storage.ForgivingManifestStaticFilesStorage" # TODO: update environment variables in EB and delete the EFS code here MEDIA_ROOT = os.path.join(BASE_DIR, "media") if "FILE_SYSTEM_ID" in os.environ: # AWS EFS for media MEDIA_ROOT = "/cobalt-media" MEDIA_URL = "/media/" # Allow override of media root MEDIA_ROOT = set_value("MEDIA_ROOT_OVERRIDE", MEDIA_ROOT) LOGIN_REDIRECT_URL = "/dashboard" LOGOUT_REDIRECT_URL = "/" MESSAGE_TAGS = { messages.DEBUG: "alert-info", messages.INFO: "alert-info", messages.SUCCESS: "alert-success", messages.WARNING: "alert-warning", messages.ERROR: "alert-danger", } EMAIL_SUBJECT_PREFIX = f"[{COBALT_HOSTNAME}] " GLOBAL_ORG = "ABF" GLOBAL_TITLE = "My ABF" GLOBAL_CONTACT = "/support/contact-logged-in" GLOBAL_ABOUT = "https://abf.com.au" GLOBAL_COOKIES = "/support/cookies" GLOBAL_PRODUCTION = "www.myabf.com.au" GLOBAL_TEST = "test.myabf.com.au" GLOBAL_PRIVACY = "https://abf.com.au/privacy" GLOBAL_CURRENCY_SYMBOL = "$" GLOBAL_CURRENCY_NAME = "Dollar" BRIDGE_CREDITS = "Bridge Credits" # Payments auto amounts AUTO_TOP_UP_LOW_LIMIT = 20 AUTO_TOP_UP_DEFAULT_AMT = 100 AUTO_TOP_UP_MIN_AMT = 50 AUTO_TOP_UP_MAX_AMT = 2000 # django-summernote provides the rich text entry fields # SUMMERNOTE_THEME = 'bs4' SUMMERNOTE_CONFIG = { "iframe": False, "summernote": { "fontSizes": ["8", "9", "10", "11", "12", "14", "16", "18", "24", "36"], "lineHeights": ["1", "0.5", "0"], "airMode": False, "width": "100%", "height": "600", "lang": None, "spellCheck": True, "toolbar": [ ["style", ["style"]], ["fontsize", ["fontsize"]], ["font", ["bold", "italic", "underline"]], ["fontname", ["fontname"]], ["height", ["height"]], ["color", ["color"]], ["para", ["ul", "ol", "paragraph"]], ["table", ["table"]], ["insert", ["link", "picture", "hr"]], [ "cards", [ "specialcharsspades", "specialcharshearts", "specialcharsdiamonds", "specialcharsclubs", "specialcharshand", ], ], ["view", ["fullscreen", "codeview"]], ["help", ["help"]], ], }, "attachment_require_authentication": True, "disable_attachment": False, "attachment_absolute_uri": True, "attachment_filesize_limit": 20000000, } # COB-947: Removing hard coding of system account ids # Default user to be the everyone user for RBAC RBAC_EVERYONE = int(set_value("RBAC_EVERYONE_ID", 1)) # TBA User for Event entries TBA_PLAYER = int(set_value("TBA_PLAYER_ID", 2)) # ABF User for Announcements ABF_USER = int(set_value("ABF_USER_ID", 3)) # System accounts ALL_SYSTEM_ACCOUNTS = [RBAC_EVERYONE, TBA_PLAYER, ABF_USER] ALL_SYSTEM_ACCOUNT_SYSTEM_NUMBERS = [0, 1, 2] # ABF Organisation - used for Settlement transactions and other things. Assumed to be the first thing created. ABF_ORG = 1 # Org id for the system account GLOBAL_ORG_ID = 1 # Logout users every 100 years or so SESSION_EXPIRE_AT_BROWSER_CLOSE = False SESSION_COOKIE_AGE = 5256000000 # Upgrade to Django 3.2 requires this setting. Not clear if BigAutoField would be better # This is the current default so using that DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # The bleach module is used to clean user supplied HTML, mainly for textfields from Summernote BLEACH_ALLOWED_TAGS = [ "a", "abbr", "acronym", "b", "u", "blockquote", "code", "em", "i", "li", "ol", "strong", "ul", "h1", "h2", "h3", "h4", "h5", "h6", "br", "p", "span", "font", "pre", "table", "tbody", "tr", "td", "img", "hr", ] BLEACH_ALLOWED_ATTRIBUTES = [ "title", "href", "style", "color", "class", "target", "src", "data-filename", ] BLEACH_ALLOWED_STYLES = [ "text-decoration", "text-align", "line-height", "font-size", "font-family", "background-color", "width", "float", ] # Group used to manage the helpdesk staff RBAC_HELPDESK_GROUP = "rbac.orgs.abf.abf_roles.helpdesk_staff" # ABF States - org_id, name, state ABF_STATES = { 1801: ("BFACT", "ACT"), 2001: ("NSWBA", "NSW"), 8901: ("NTBA", "NT"), 4501: ("QBA", "QLD"), 5700: ("SABF", "SA"), 7801: ("TBA", "TAS"), 3301: ("VBA", "VIC"), 6751: ("BAWA", "WA"), } # On Elastic Beanstalk the userid that we run under seems to change. Set all permissions to 777 as there is # no sensitive information stored here, and only Django can access the files directly. FILE_UPLOAD_PERMISSIONS = 0o777 FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o777 # Check for default value of COBALT_HOSTNAME. If this is not set to something else then we could be on Read The Docs # Read the Docs will fail writing to the log file if COBALT_HOSTNAME == "127.0.0.1:8000": LOGFILE = "/tmp/cobalt.log" else: LOGFILE = "/var/log/cobalt.log" LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "cobalt": { "format": "[%(levelname)-8s] %(asctime)s [%(module)s %(funcName)s %(lineno)d] %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", }, }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "cobalt", }, "file": { "class": "logging.FileHandler", "filename": LOGFILE, "formatter": "cobalt", }, }, "loggers": { "cobalt": { "handlers": ["console", "file"], "level": "DEBUG", }, "post_office": { "handlers": ["console", "file"], "level": "DEBUG", }, }, } GOOGLE_APPLICATION_CREDENTIALS = set_value("GOOGLE_APPLICATION_CREDENTIALS", "NOTSET") FIREBASE_APP = initialize_app() # Check if we want to enable the debug toolbar DEBUG_TOOLBAR_ENABLED = set_value("DEBUG_TOOLBAR_ENABLED", False) if DEBUG and DEBUG_TOOLBAR_ENABLED: INSTALLED_APPS.append("debug_toolbar") MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware") INTERNAL_IPS = [ "127.0.0.1", ]