From 80f8a524efe8806354a64f5e6016fa3973ff506d Mon Sep 17 00:00:00 2001 From: fliiiix Date: Tue, 21 May 2024 10:20:35 +0200 Subject: [PATCH 1/3] Strip whitespace in comma lists This makes configurations valid which contain one or more extra spaces. Example: CSRF_TRUSTED_ORIGINS = "https://foo.bar.example, http://foo.example" Would be invalid. --- recipes/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/recipes/settings.py b/recipes/settings.py index c2d858936..25caf01b3 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -46,7 +46,7 @@ SPACE_DEFAULT_MAX_USERS = int(os.getenv('SPACE_DEFAULT_MAX_USERS', 0)) SPACE_DEFAULT_MAX_FILES = int(os.getenv('SPACE_DEFAULT_MAX_FILES', 0)) SPACE_DEFAULT_ALLOW_SHARING = bool(int(os.getenv('SPACE_DEFAULT_ALLOW_SHARING', True))) -INTERNAL_IPS = os.getenv('INTERNAL_IPS').split(',') if os.getenv('INTERNAL_IPS') else ['127.0.0.1'] +INTERNAL_IPS = [ip.strip() for ip in os.getenv('INTERNAL_IPS').split(',')] if os.getenv('INTERNAL_IPS') else ['127.0.0.1'] # allow djangos wsgi server to server mediafiles GUNICORN_MEDIA = bool(int(os.getenv('GUNICORN_MEDIA', False))) @@ -69,10 +69,10 @@ FORCE_THEME_FROM_SPACE = int(os.getenv('FORCE_THEME_FROM_SPACE', 0)) # minimum interval that users can set for automatic sync of shopping lists SHOPPING_MIN_AUTOSYNC_INTERVAL = int(os.getenv('SHOPPING_MIN_AUTOSYNC_INTERVAL', 5)) -ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(',') if os.getenv('ALLOWED_HOSTS') else ['*'] +ALLOWED_HOSTS = [host.strip() for host in os.getenv('ALLOWED_HOSTS').split(',')] if os.getenv('ALLOWED_HOSTS') else ['*'] if os.getenv('CSRF_TRUSTED_ORIGINS'): - CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS').split(',') + CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in os.getenv('CSRF_TRUSTED_ORIGINS').split(',')] if CORS_ORIGIN_ALLOW_ALL := os.getenv('CORS_ORIGIN_ALLOW_ALL') is not None: print('DEPRECATION WARNING: Environment var "CORS_ORIGIN_ALLOW_ALL" is deprecated. Please use "CORS_ALLOW_ALL_ORIGINS."') @@ -184,7 +184,7 @@ except Exception: if DEBUG: print('ERROR failed to initialize plugins') -SOCIAL_PROVIDERS = os.getenv('SOCIAL_PROVIDERS').split(',') if os.getenv('SOCIAL_PROVIDERS') else [] +SOCIAL_PROVIDERS = [social.strip() for social in os.getenv('SOCIAL_PROVIDERS').split(',')] if os.getenv('SOCIAL_PROVIDERS') else [] SOCIALACCOUNT_EMAIL_VERIFICATION = 'none' INSTALLED_APPS = INSTALLED_APPS + SOCIAL_PROVIDERS From 6257f6ffb7bbe28a3c8025f89308616318759bc1 Mon Sep 17 00:00:00 2001 From: fliiiix Date: Tue, 21 May 2024 10:53:34 +0200 Subject: [PATCH 2/3] Refactor extract settings to helper functions --- recipes/settings.py | 77 ++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/recipes/settings.py b/recipes/settings.py index 25caf01b3..7a2661768 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -22,6 +22,19 @@ from django.contrib import messages from django.utils.translation import gettext_lazy as _ from dotenv import load_dotenv +def extract_bool(env_key, default): + return bool(int(os.getenv(env_key, default))) + +def extract_comma_list(env_key, default=None): + if os.getenv(env_key): + return [item.strip() for item in os.getenv(env_key).split(',')] + else: + if default: + return [default] + else: + return [] + + load_dotenv() BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SCRIPT_NAME = os.getenv('SCRIPT_NAME', '') @@ -33,35 +46,35 @@ STATIC_URL = os.getenv('STATIC_URL', '/static/') STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") # Get vars from .env files -SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV' +SECRET_KEY = os.getenv('SECRET_KEY', 'INSECURE_STANDARD_KEY_SET_IN_ENV') -DEBUG = bool(int(os.getenv('DEBUG', True))) -DEBUG_TOOLBAR = bool(int(os.getenv('DEBUG_TOOLBAR', True))) +DEBUG = extract_bool('DEBUG', True) +DEBUG_TOOLBAR = extract_bool('DEBUG_TOOLBAR', True) -SOCIAL_DEFAULT_ACCESS = bool(int(os.getenv('SOCIAL_DEFAULT_ACCESS', False))) +SOCIAL_DEFAULT_ACCESS = extract_bool('SOCIAL_DEFAULT_ACCESS', False) SOCIAL_DEFAULT_GROUP = os.getenv('SOCIAL_DEFAULT_GROUP', 'guest') SPACE_DEFAULT_MAX_RECIPES = int(os.getenv('SPACE_DEFAULT_MAX_RECIPES', 0)) SPACE_DEFAULT_MAX_USERS = int(os.getenv('SPACE_DEFAULT_MAX_USERS', 0)) SPACE_DEFAULT_MAX_FILES = int(os.getenv('SPACE_DEFAULT_MAX_FILES', 0)) -SPACE_DEFAULT_ALLOW_SHARING = bool(int(os.getenv('SPACE_DEFAULT_ALLOW_SHARING', True))) +SPACE_DEFAULT_ALLOW_SHARING = extract_bool('SPACE_DEFAULT_ALLOW_SHARING', True) -INTERNAL_IPS = [ip.strip() for ip in os.getenv('INTERNAL_IPS').split(',')] if os.getenv('INTERNAL_IPS') else ['127.0.0.1'] +INTERNAL_IPS = extract_comma_list('INTERNAL_IPS', '127.0.0.1') # allow djangos wsgi server to server mediafiles -GUNICORN_MEDIA = bool(int(os.getenv('GUNICORN_MEDIA', False))) +GUNICORN_MEDIA = extract_bool('GUNICORN_MEDIA', False) if os.getenv('REVERSE_PROXY_AUTH') is not None: print('DEPRECATION WARNING: Environment var "REVERSE_PROXY_AUTH" is deprecated. Please use "REMOTE_USER_AUTH".') - REMOTE_USER_AUTH = bool(int(os.getenv('REVERSE_PROXY_AUTH', False))) + REMOTE_USER_AUTH = extract_bool('REVERSE_PROXY_AUTH', False) else: - REMOTE_USER_AUTH = bool(int(os.getenv('REMOTE_USER_AUTH', False))) + REMOTE_USER_AUTH = extract_bool('REMOTE_USER_AUTH', False) # default value for user preference 'comment' -COMMENT_PREF_DEFAULT = bool(int(os.getenv('COMMENT_PREF_DEFAULT', True))) -FRACTION_PREF_DEFAULT = bool(int(os.getenv('FRACTION_PREF_DEFAULT', False))) -KJ_PREF_DEFAULT = bool(int(os.getenv('KJ_PREF_DEFAULT', False))) -STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True))) +COMMENT_PREF_DEFAULT = extract_bool('COMMENT_PREF_DEFAULT', True) +FRACTION_PREF_DEFAULT = extract_bool('FRACTION_PREF_DEFAULT', False) +KJ_PREF_DEFAULT = extract_bool('KJ_PREF_DEFAULT', False) +STICKY_NAV_PREF_DEFAULT = extract_bool('STICKY_NAV_PREF_DEFAULT', True) MAX_OWNED_SPACES_PREF_DEFAULT = int(os.getenv('MAX_OWNED_SPACES_PREF_DEFAULT', 100)) UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0)) FORCE_THEME_FROM_SPACE = int(os.getenv('FORCE_THEME_FROM_SPACE', 0)) @@ -69,16 +82,14 @@ FORCE_THEME_FROM_SPACE = int(os.getenv('FORCE_THEME_FROM_SPACE', 0)) # minimum interval that users can set for automatic sync of shopping lists SHOPPING_MIN_AUTOSYNC_INTERVAL = int(os.getenv('SHOPPING_MIN_AUTOSYNC_INTERVAL', 5)) -ALLOWED_HOSTS = [host.strip() for host in os.getenv('ALLOWED_HOSTS').split(',')] if os.getenv('ALLOWED_HOSTS') else ['*'] - -if os.getenv('CSRF_TRUSTED_ORIGINS'): - CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in os.getenv('CSRF_TRUSTED_ORIGINS').split(',')] +ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '*') +CSRF_TRUSTED_ORIGINS = extract_comma_list('CSRF_TRUSTED_ORIGINS') if CORS_ORIGIN_ALLOW_ALL := os.getenv('CORS_ORIGIN_ALLOW_ALL') is not None: print('DEPRECATION WARNING: Environment var "CORS_ORIGIN_ALLOW_ALL" is deprecated. Please use "CORS_ALLOW_ALL_ORIGINS."') CORS_ALLOW_ALL_ORIGINS = CORS_ORIGIN_ALLOW_ALL else: - CORS_ALLOW_ALL_ORIGINS = bool(int(os.getenv("CORS_ALLOW_ALL_ORIGINS", True))) + CORS_ALLOW_ALL_ORIGINS = extract_bool("CORS_ALLOW_ALL_ORIGINS", True) LOGIN_REDIRECT_URL = "index" LOGOUT_REDIRECT_URL = "index" @@ -98,7 +109,7 @@ HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '') FDC_API_KEY = os.getenv('FDC_API_KEY', 'DEMO_KEY') GOOGLE_AI_API_KEY = os.getenv('GOOGLE_AI_API_KEY', '') -SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False))) +SHARING_ABUSE = extract_bool('SHARING_ABUSE', False) SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0)) DRF_THROTTLE_RECIPE_URL_IMPORT = os.getenv('DRF_THROTTLE_RECIPE_URL_IMPORT', '60/hour') @@ -106,7 +117,7 @@ DRF_THROTTLE_RECIPE_URL_IMPORT = os.getenv('DRF_THROTTLE_RECIPE_URL_IMPORT', '60 TERMS_URL = os.getenv('TERMS_URL', '') PRIVACY_URL = os.getenv('PRIVACY_URL', '') IMPRINT_URL = os.getenv('IMPRINT_URL', '') -HOSTED = bool(int(os.getenv('HOSTED', False))) +HOSTED = extract_bool('HOSTED', False) MESSAGE_TAGS = {messages.ERROR: 'danger'} @@ -184,7 +195,7 @@ except Exception: if DEBUG: print('ERROR failed to initialize plugins') -SOCIAL_PROVIDERS = [social.strip() for social in os.getenv('SOCIAL_PROVIDERS').split(',')] if os.getenv('SOCIAL_PROVIDERS') else [] +SOCIAL_PROVIDERS = extract_comma_list('SOCIAL_PROVIDERS') SOCIALACCOUNT_EMAIL_VERIFICATION = 'none' INSTALLED_APPS = INSTALLED_APPS + SOCIAL_PROVIDERS @@ -202,11 +213,11 @@ except ValueError: SESSION_COOKIE_DOMAIN = os.getenv('SESSION_COOKIE_DOMAIN', None) SESSION_COOKIE_NAME = os.getenv('SESSION_COOKIE_NAME', 'sessionid') -ENABLE_SIGNUP = bool(int(os.getenv('ENABLE_SIGNUP', False))) +ENABLE_SIGNUP = extract_bool('ENABLE_SIGNUP', False) -ENABLE_METRICS = bool(int(os.getenv('ENABLE_METRICS', False))) +ENABLE_METRICS = extract_bool('ENABLE_METRICS', False) -ENABLE_PDF_EXPORT = bool(int(os.getenv('ENABLE_PDF_EXPORT', False))) +ENABLE_PDF_EXPORT = extract_bool('ENABLE_PDF_EXPORT', False) EXPORT_FILE_CACHE_DURATION = int(os.getenv('EXPORT_FILE_CACHE_DURATION', 600)) MIDDLEWARE = [ @@ -228,10 +239,10 @@ if DEBUG_TOOLBAR: MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware', ) INSTALLED_APPS += ('debug_toolbar', ) -SORT_TREE_BY_NAME = bool(int(os.getenv('SORT_TREE_BY_NAME', False))) -DISABLE_TREE_FIX_STARTUP = bool(int(os.getenv('DISABLE_TREE_FIX_STARTUP', False))) +SORT_TREE_BY_NAME = extract_bool('SORT_TREE_BY_NAME', False) +DISABLE_TREE_FIX_STARTUP = extract_bool('DISABLE_TREE_FIX_STARTUP', False) -if bool(int(os.getenv('SQL_DEBUG', False))): +if extract_bool('SQL_DEBUG', False): MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware', ) if ENABLE_METRICS: @@ -249,7 +260,7 @@ if LDAP_AUTH: AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend') AUTH_LDAP_SERVER_URI = os.getenv('AUTH_LDAP_SERVER_URI') - AUTH_LDAP_START_TLS = bool(int(os.getenv('AUTH_LDAP_START_TLS', False))) + AUTH_LDAP_START_TLS = extract_bool('AUTH_LDAP_START_TLS', False) AUTH_LDAP_BIND_DN = os.getenv('AUTH_LDAP_BIND_DN') AUTH_LDAP_BIND_PASSWORD = os.getenv('AUTH_LDAP_BIND_PASSWORD') AUTH_LDAP_USER_SEARCH = LDAPSearch( @@ -262,7 +273,7 @@ if LDAP_AUTH: 'last_name': 'sn', 'email': 'mail', } - AUTH_LDAP_ALWAYS_UPDATE_USER = bool(int(os.getenv('AUTH_LDAP_ALWAYS_UPDATE_USER', True))) + AUTH_LDAP_ALWAYS_UPDATE_USER = extract_bool('AUTH_LDAP_ALWAYS_UPDATE_USER', True) AUTH_LDAP_CACHE_TIMEOUT = int(os.getenv('AUTH_LDAP_CACHE_TIMEOUT', 3600)) if 'AUTH_LDAP_TLS_CACERTFILE' in os.environ: AUTH_LDAP_GLOBAL_OPTIONS = {ldap.OPT_X_TLS_CACERTFILE: os.getenv('AUTH_LDAP_TLS_CACERTFILE')} @@ -568,7 +579,7 @@ if os.getenv('S3_ACCESS_KEY', ''): AWS_ACCESS_KEY_ID = os.getenv('S3_ACCESS_KEY', '') AWS_SECRET_ACCESS_KEY = os.getenv('S3_SECRET_ACCESS_KEY', '') AWS_STORAGE_BUCKET_NAME = os.getenv('S3_BUCKET_NAME', '') - AWS_QUERYSTRING_AUTH = bool(int(os.getenv('S3_QUERYSTRING_AUTH', True))) + AWS_QUERYSTRING_AUTH = extract_bool('S3_QUERYSTRING_AUTH', True) AWS_QUERYSTRING_EXPIRE = int(os.getenv('S3_QUERYSTRING_EXPIRE', 3600)) AWS_S3_SIGNATURE_VERSION = os.getenv('S3_SIGNATURE_VERSION', 's3v4') AWS_S3_REGION_NAME = os.getenv('S3_REGION_NAME', None) @@ -604,8 +615,8 @@ EMAIL_HOST = os.getenv('EMAIL_HOST', '') EMAIL_PORT = int(os.getenv('EMAIL_PORT', 25)) EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '') EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '') -EMAIL_USE_TLS = bool(int(os.getenv('EMAIL_USE_TLS', False))) -EMAIL_USE_SSL = bool(int(os.getenv('EMAIL_USE_SSL', False))) +EMAIL_USE_TLS = extract_bool('EMAIL_USE_TLS', False) +EMAIL_USE_SSL = extract_bool('EMAIL_USE_SSL', False) DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost') ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv('ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix @@ -621,7 +632,7 @@ ACCOUNT_RATE_LIMITS = { "login": "5/m/ip", } -DISABLE_EXTERNAL_CONNECTORS = bool(int(os.getenv('DISABLE_EXTERNAL_CONNECTORS', False))) +DISABLE_EXTERNAL_CONNECTORS = extract_bool('DISABLE_EXTERNAL_CONNECTORS', False) EXTERNAL_CONNECTORS_QUEUE_SIZE = int(os.getenv('EXTERNAL_CONNECTORS_QUEUE_SIZE', 100)) # ACCOUNT_SIGNUP_FORM_CLASS = 'cookbook.forms.AllAuthSignupForm' From 10b4c3da05001bae1098f1847f2dbf40280ac9d5 Mon Sep 17 00:00:00 2001 From: fliiiix Date: Tue, 21 May 2024 10:56:54 +0200 Subject: [PATCH 3/3] Use getenv default instead of or syntax --- recipes/settings.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/recipes/settings.py b/recipes/settings.py index 7a2661768..d1dcac4bb 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -415,14 +415,14 @@ WSGI_APPLICATION = 'recipes.wsgi.application' # Database # Load settings from env files -DATABASE_URL = os.getenv('DATABASE_URL') or None -DB_OPTIONS = os.getenv('DB_OPTIONS') or None -DB_ENGINE = os.getenv('DB_ENGINE') or None -POSTGRES_HOST = os.getenv('POSTGRES_HOST') or None -POSTGRES_PORT = os.getenv('POSTGRES_PORT') or None -POSTGRES_USER = os.getenv('POSTGRES_USER') or None -POSTGRES_PASSWORD = os.getenv('POSTGRES_PASSWORD') or None -POSTGRES_DB = os.getenv('POSTGRES_DB') or None +DATABASE_URL = os.getenv('DATABASE_URL', None) +DB_OPTIONS = os.getenv('DB_OPTIONS', None) +DB_ENGINE = os.getenv('DB_ENGINE', None) +POSTGRES_HOST = os.getenv('POSTGRES_HOST', None) +POSTGRES_PORT = os.getenv('POSTGRES_PORT', None) +POSTGRES_USER = os.getenv('POSTGRES_USER', None) +POSTGRES_PASSWORD = os.getenv('POSTGRES_PASSWORD', None) +POSTGRES_DB = os.getenv('POSTGRES_DB', None) def setup_database(db_url=None, db_options=None, db_engine=None, pg_host=None, pg_port=None, pg_user=None, pg_password=None, pg_db=None):