diff --git a/.dockerignore b/.dockerignore index 2a717da57..b0631b58d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,8 @@ # Version Control .git +**settings_local.py + # Python *.pyc *.pyo diff --git a/.env.sample b/.env.sample deleted file mode 100644 index a3f7a5bb4..000000000 --- a/.env.sample +++ /dev/null @@ -1,9 +0,0 @@ -# -# Required -# - -# From which directory to pull project-specific resources -# Examples: -CUSTOM_ASSET_DIR=core-cms -CUSTOM_ASSET_DIR=frontera-cms -CUSTOM_ASSET_DIR=protx-cms diff --git a/.gitignore b/.gitignore index 391987d8b..e59994e06 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ repo_name.var settings.json taccsite_cms/secrets.py *.custom.yml +*settings_custom*.py +*settings_local*.py # Makefile var cms_name.var diff --git a/Dockerfile b/Dockerfile index 13bc3f8db..2cbe9c057 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,4 +25,4 @@ WORKDIR /code RUN pip3 install --no-cache-dir -r requirements.txt # build assets -RUN npm ci && npm run settings && npm run build +RUN npm ci && npm run build diff --git a/README.md b/README.md index 899bcc0ca..114e8d606 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The TACC CORE-CMS can be run using Docker and Docker Compose, both locally and i CUSTOM_ASSET_DIR=example-cms ``` - \* Where `example-cms` is the project to be run. _See [(Optional) Custom Resources per CMS Project](#optional-custom-resources-per-cms-project). Refer to `.env.sample` for other settings._ + \* Where `example-cms` is the project to be run. _See [(Optional) Custom Resources per CMS Project](#optional-custom-resources-per-cms-project)._ 1. Initialize / Update submodules. 1. `git submodule init`\ _(Adds `cms-site-resources` repo as submodule at `taccsite_custom/`. Only necessary once per `CORE-cms` repo clone.)_ diff --git a/conf/css/.postcssrc.yml b/conf/css/.postcssrc.yml index 4dd78259b..7938a3ed5 100644 --- a/conf/css/.postcssrc.yml +++ b/conf/css/.postcssrc.yml @@ -13,6 +13,7 @@ plugins: postcss-preset-env: # SEE: https://github.com/csstools/postcss-preset-env#features stage: false + # SEE: https://github.com/csstools/postcss-preset-env/blob/master/src/lib/plugins-by-id.js#L35 features: custom-media-queries: true media-query-ranges: true diff --git a/package.json b/package.json index b5e56c1a8..3439077e8 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "license": "MIT", "description": "The core CMS codebase for all new and updated TACC CMS sites.", "scripts": { + "prebuild": "python3 taccsite_cms/settings_to_json.py", "build": "npm run build:css", - "build:css": "npm run settings && node postcss.js", - "watch": "npm-watch", - "settings": "python3 taccsite_cms/settings_to_json.py" + "build:css": "node postcss.js", + "watch": "npm-watch" }, "// scripts": { "prebuild": "Export Django settings to JSON (for Node to use)", diff --git a/taccsite_cms/contrib/bootstrap4_djangocms_link/cms_plugins.py b/taccsite_cms/contrib/bootstrap4_djangocms_link/cms_plugins.py index 8fc6a7db2..6b287e6a8 100644 --- a/taccsite_cms/contrib/bootstrap4_djangocms_link/cms_plugins.py +++ b/taccsite_cms/contrib/bootstrap4_djangocms_link/cms_plugins.py @@ -6,7 +6,7 @@ # SEE: https://github.com/django-cms/djangocms-bootstrap4/pull/138 import copy - from django.utils.translation import gettext_lazy as _ + from django.utils.translation import gettext as _ from cms.plugin_pool import plugin_pool diff --git a/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py b/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py index 5f7bf7f42..262d4e623 100644 --- a/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py +++ b/taccsite_cms/contrib/bootstrap4_djangocms_picture/cms_plugins.py @@ -2,7 +2,7 @@ # FAQ: Bootstrap Image plugin has features not desirable in TACC plugins # FAQ: We must not break sites that already use Bootstrap Image plugin try: - from django.utils.translation import gettext_lazy as _ + from django.utils.translation import gettext as _ from cms.plugin_pool import plugin_pool diff --git a/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py b/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py index fece7a03f..82a7db1c9 100644 --- a/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py +++ b/taccsite_cms/contrib/taccsite_data_list/cms_plugins.py @@ -1,6 +1,6 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ from taccsite_cms.contrib.constants import TEXT_FOR_NESTED_PLUGIN_CONTENT_SWAP from taccsite_cms.contrib.helpers import concat_classnames diff --git a/taccsite_cms/contrib/taccsite_data_list/models.py b/taccsite_cms/contrib/taccsite_data_list/models.py index 750698739..431e874e8 100644 --- a/taccsite_cms/contrib/taccsite_data_list/models.py +++ b/taccsite_cms/contrib/taccsite_data_list/models.py @@ -1,7 +1,7 @@ from cms.models.pluginmodel import CMSPlugin from django.db import models -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ from djangocms_attributes_field import fields diff --git a/taccsite_cms/contrib/taccsite_sample/cms_plugins.py b/taccsite_cms/contrib/taccsite_sample/cms_plugins.py index f56b1d5a9..86b4d1e08 100644 --- a/taccsite_cms/contrib/taccsite_sample/cms_plugins.py +++ b/taccsite_cms/contrib/taccsite_sample/cms_plugins.py @@ -1,7 +1,7 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool from cms.models.pluginmodel import CMSPlugin -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ from django.utils.encoding import force_text from .models import TaccsiteSample diff --git a/taccsite_cms/contrib/taccsite_system_monitor/cms_plugins.py b/taccsite_cms/contrib/taccsite_system_monitor/cms_plugins.py index 2202609fa..d85811068 100644 --- a/taccsite_cms/contrib/taccsite_system_monitor/cms_plugins.py +++ b/taccsite_cms/contrib/taccsite_system_monitor/cms_plugins.py @@ -1,6 +1,6 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ from taccsite_cms.contrib.helpers import concat_classnames diff --git a/taccsite_cms/contrib/taccsite_system_monitor/models.py b/taccsite_cms/contrib/taccsite_system_monitor/models.py index 91dbae3d6..6820012d6 100644 --- a/taccsite_cms/contrib/taccsite_system_monitor/models.py +++ b/taccsite_cms/contrib/taccsite_system_monitor/models.py @@ -1,5 +1,5 @@ from cms.models.pluginmodel import CMSPlugin -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ from django.db import models diff --git a/taccsite_cms/default_secrets.py b/taccsite_cms/default_secrets.py deleted file mode 100644 index 6e2a1158c..000000000 --- a/taccsite_cms/default_secrets.py +++ /dev/null @@ -1,316 +0,0 @@ -# TACC CMS SITE TEMPLATE SETTINGS. -# DEFAULT VALUES. -# CHANGE BEFOR DEV/PREPROD/PRODUCTION DEPLOYMENT. - -######################## -# DJANGO SETTINGS -######################## - -_SECRET_KEY = 'replacethiswithareallysecureandcomplexsecretkeystring' -_DEBUG = True # False for Prod. -_CONSOLE_LOG_ENABLED = False # Boolean check to turn on/off console logging statements. - -# Specify allowed hosts or use an asterisk to allow any host and simplify the config. -# _ALLOWED_HOSTS = ['hostname.tacc.utexas.edu', 'host.ip.v4.address', '0.0.0.0', 'localhost', '127.0.0.1'] # In production. -_ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1', 'localhost', '*'] # In development. - -# Boolean check to see if ldap is being used by the site. -# Requires django-auth-ldap ≥ 2.0.0 -_LDAP_ENABLED = False - -# Boolean check to determine the appropriate database settings when using containers. -_USING_CONTAINERS = True - -######################## -# DATABASE SETTINGS -######################## - -if _USING_CONTAINERS: - # used in container deployments. - _DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'PORT': '5432', - 'NAME': 'taccsite', - 'USER': 'postgresadmin', - 'PASSWORD': 'taccforever', # Change before live deployment. - 'HOST': 'core_cms_postgres' - } - } -else: - # used in local dev, venv or manual deployments. - _DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'PORT': '5432', - 'NAME': 'taccsite', - 'USER': 'postgresadmin', - 'PASSWORD': 'taccforever', # Change before live deployment. - 'HOST': 'localhost' - } - } - -######################## -# DJANGO CMS SETTINGS -######################## - -# CMS Site (allows for multiple sites on a single CMS) -_SITE_ID = 1 -_CMS_TEMPLATES = ( - # Customize this list in per-project `secrets.py` - # FAQ: First template is default template - # REF: http://docs.django-cms.org/en/latest/how_to/install.html#templates - - # For standard pages (has Container and Breadcrumbs) - ('standard.html', 'Standard'), - # For content that spans full window width (no Container nor Breadcrumbs) - ('fullwidth.html', 'Full Width'), - - # Any project that needs per-project styles must have a custom template - # FAQ: This is a tedious solution until a cleaner solution is devised - # TODO: Automate per-project asset load and update exisitng sites as needed - # ('name-of-project/templates/fullwidth.html', 'Fullwidth (Custom)'), - # NOTE: If project later uses custom template, then for that project: - # 1. Rename standard default template to "DEPRECATED […]". - # 2. Update all pages to use the custom default template. - # 3. Disable standard default template (remove from `_CMS_TEMPLATES`). - # ('fullwidth.html', 'DEPRECATED Fullwidth'), - - # Any portal whose homepage has NO design must enable and use this template - # ('home_portal.html', 'Standard Portal Homepage'), - - # All portals should enable all of these templates - # FAQ: If this becomes mandatory, then in `settings.py`: - # `if _PORTAL: [ manually add these entries ]` - # ('guide.html', 'Guide'), - # ('guides/getting_started.html', 'Guide: Getting Started'), - # ('guides/data_transfer.html', 'Guide: Data Transfer'), - # ('guides/data_transfer.globus.html', 'Guide: Globus Data Transfer'), - # ('guides/portal_technology.html', 'Guide: Portal Technology Stack'), - - # For now, only Core portal should enable this template - # FAQ: We know not yet how to auto-replicate pages and plugins across sites - # ('style_guide.html', 'Style Guide'), -) - -######################## -# GOOGLE ANALYTICS -######################## - -# To use during dev, Tracking Protection in browser needs to be turned OFF. -_GOOGLE_ANALYTICS_PROPERTY_ID = "UA-123ABC@%$&-#" -_GOOGLE_ANALYTICS_PRELOAD = True - -######################## -# CMS FORMS -######################## - -# Create CMS Forms -# SEE: https://pypi.org/project/djangocms-forms/ -# SEE: https://www.google.com/recaptcha/admin/create -_DJANGOCMS_FORMS_RECAPTCHA_PUBLIC_KEY = "" -_DJANGOCMS_FORMS_RECAPTCHA_SECRET_KEY = "" - -######################## -# ELASTICSEARCH -######################## - -_ES_AUTH = 'username:password' -_ES_HOSTS = 'http://elasticsearch:9200' -_ES_INDEX_PREFIX = 'cms-dev-{}' -_ES_DOMAIN = 'http://localhost:8000' - -######################## -# FEATURES -######################## - -""" -Features for the CMS that can be turned either ON or OFF - -Usage: - -- For baked-in features, like BRANDING or PORTAL, see relevant section instead. -- For optional features, look below, and enable feature(s) via _FEATURES list. - -Baked-In Feature Setting Example. - -# Desctipion of feature X -# SEE: [link to user/div guide about feature] -_FEATURE_A = "someValue" - -Optional Feature Toggle Example. - -_FEATURES = { - # Desctipion of feature X - # SEE: [link to user/dev guide about feature] - "X": True, - - # Desctipion of feature Y - # SEE: [link to user/dev guide about feature] - "Y": False, - - # Desctipion of feature Z - # SEE: [link to user/dev guide about feature] - "Z": True, -} - -""" - -_FEATURES = { - # Blog/News & Social Media Metadata - # GL-42: Split this into two features - # SEE: https://confluence.tacc.utexas.edu/x/EwDeCg - # SEE: https://confluence.tacc.utexas.edu/x/FAA9Cw - "blog": False, -} - -######################## -# BRANDING & LOGOS & FAVICON -######################## -# TODO: GH-59: Use Dict Not Array for Branding Settings - -# Visual theme for the CMS - -""" -Optional theming of CMS (certain themes may only affect some elements) -Usage: -- None (standard theme) -- 'has-dark-logo' -""" - -_THEME = None - -# Branding settings for portal and navigation. - -""" -Additional Branding and Portal Logos for Partner & Affiliate Organizations - -Usage: - -- For each beand used in the templating, add corresponding new settings values to this file (see example below). -- New branding settings must be added to the _BRANDING list to render in the template. -- The order of the _BRANDING list determines the rendering order of the elements in the template. -- The portal logo setting must be assigned to the _LOGO variable to render in the template. -- The following VALUES for new elements set in the configuration object must exist in the portal css as well: - - Any new selectors or css styles (add to /taccsite_cms/static/site_cms/css/src/_imports/branding_logos.css) - - Image files being references (add to /taccsite_cms/static/site_cms/img/org_logos) - -Values to populate (for an array): - -_SETTING_NAME = [ # The name of the branding or logo config setting object. - "org_name", # The name of the organization the branding belongs too. - "img_file_src", # Path and filename relative to the static files folder. - "img_element_classes", # The list of selectors to apply to the rendered element, these need to exist in the css/scss. - "a_target_url", # The href link to follow when clicked, use "/" for portal logos. - "a_target_type", # The target to open the new link in, use _blank for external links, _self for internal links. - "alt_text", # The text to read or render for web assistance standards. - "cors_setting", # The CORS setting for the image, set to anonymous by default. - "visibility" # Toggles wether or not to display the element in the template, use True to render, False to hide. -] - -Values to populate (for a dict): - -_SETTING_NAME = { # The name of the favicon config setting object. - "img_file_src": "…", # Path and filename relative to the static files folder. -} - -Branding Configuration Example. - -_ANORG_BRANDING = [ - "anorg", - "site_cms/img/org_logos/anorg-logo.png" - "branding-anorg", - "https://www.anorg.com/" - "_blank", - "ANORG Logo", - "anonymous", - "True" -] - -Logo Configuration Example. - -_ANORG_LOGO = [ - "anorg", - "site_cms/img/org_logos/anorg-logo.png" - "branding-anorg", - "/" - "_self", - "ANORG Logo", - "anonymous", - "True" -] - -Favicon Configuration Example. - -_ANORG_FAVICON = { - "img_file_src": "site_cms/img/favicons/favicon.ico" -} -""" - -######################## -# BRANDING - -_TACC_BRANDING = [ - "tacc", - "site_cms/img/org_logos/tacc-white.png", - "branding-tacc", # Unused but kept to retain index count # GH-59: Remove - "https://www.tacc.utexas.edu/", - "_blank", - "TACC Logo", - "anonymous", - "True" -] - -_UTEXAS_BRANDING = [ - "utexas", - "site_cms/img/org_logos/utaustin-white.png", - "branding-utaustin", # Unused but kept to retain index count # GH-59: Remove - "https://www.utexas.edu/", - "_blank", - "University of Texas at Austin Logo", - "anonymous", - "True" -] - -_NSF_BRANDING = [ - "nsf", - "site_cms/img/org_logos/nsf-white.png", - "branding-nsf", # Unused but kept to retain index count # GH-59: Remove - "https://www.nsf.gov/", - "_blank", - "NSF Logo", - "anonymous", - "True" -] - -_BRANDING = [ _TACC_BRANDING, _UTEXAS_BRANDING ] # Default TACC Portal. -# _BRANDING = [ _NSF_BRANDING, _TACC_BRANDING, _UTEXAS_BRANDING ] # NSF Funded TACC Portal. - -######################## -# LOGOS - -_PORTAL_LOGO = [ - "portal", - "site_cms/img/org_logos/portal.png", - "", # Unused, but kept to retain index count - "/", - "_self", - "Portal Logo", - "anonymous", - "True" -] - -_LOGO = _PORTAL_LOGO # Default Portal Logo. - -######################## -# FAVICON - -_FAVICON = { - "img_file_src": "site_cms/img/favicons/favicon.ico" -} - -######################## -# PORTAL -######################## - -_PORTAL = False # True for any CMS that is part of a Portal. diff --git a/taccsite_cms/remote_cms_auth.py b/taccsite_cms/remote_cms_auth.py new file mode 100644 index 000000000..4e767d795 --- /dev/null +++ b/taccsite_cms/remote_cms_auth.py @@ -0,0 +1,96 @@ +import inspect +from django.http import HttpResponse, HttpResponseRedirect +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth import get_user_model +from django.contrib import auth as auth +import requests +import logging +from django.conf import settings + +UserModel = get_user_model() + +logger = logging.getLogger(__name__) + + +def verify_and_auth(request): + user = auth.authenticate(request) + if user: + # User is valid. Set request.user and persist user in the session by logging the user in. + request.user = user + auth.login(request, user) + response = HttpResponseRedirect(request.GET.get('next', \ + getattr(settings, 'LOGIN_REDIRECT_URL', '/workbench/dashboard/'))) + else: + response = HttpResponseRedirect('/') + return response + + +class CorePortalAuthBackend(ModelBackend): + """ + Validates core portal session + Extends Django ModelBackend, parts of this were taken from Django's source: + https://github.com/django/django/blob/stable/2.2.x/django/contrib/auth/backends.py#L128 + """ + create_unknown_user = True + + def authenticate(self, request): + response = requests.get('{0}/api/users/auth/'.format( \ + getattr(settings, 'CEP_AUTH_VERIFICATION_ENDPOINT', 'http://django:6000')), + cookies={'coresessionid': request.COOKIES.get('coresessionid')}) + user_data = response.json() + if user_data is None or user_data['username'] is None: + return None + username = user_data['username'] + email = user_data['email'] + first_name = user_data['first_name'] + last_name = user_data['last_name'] + + if request.user.is_authenticated: + self._remove_invalid_user(request) + + if self.create_unknown_user: + user, created = UserModel._default_manager.get_or_create(**{ + UserModel.USERNAME_FIELD: username + }) + user.email = email + user.first_name = first_name + user.last_name = last_name + user.save() + if created: + args = (request, user) + try: + inspect.getcallargs(self.configure_user, request, user) + except TypeError: + args = (user,) + warnings.warn( + 'Update %s.configure_user() to accept `request` as ' + 'the first argument.' + % self.__class__.__name__, RemovedInDjango31Warning + ) + user = self.configure_user(*args) + else: + try: + user = UserModel._default_manager.get_by_natural_key(username) + except UserModel.DoesNotExist: + pass + return user if self.user_can_authenticate(user) else None + + def _remove_invalid_user(self, request): + """ + Remove the current authenticated user + """ + try: + stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, '')) + except ImportError: + # backend failed to load + auth.logout(request) + else: + if isinstance(stored_backend, RemoteUserBackend): + auth.logout(request) + + def configure_user(self, request, user): + """ + Configure a user after creation and return the updated user. + By default, return the user unmodified. + """ + return user diff --git a/taccsite_cms/settings.py b/taccsite_cms/settings.py index c81b555b9..d20c41a7e 100644 --- a/taccsite_cms/settings.py +++ b/taccsite_cms/settings.py @@ -1,13 +1,9 @@ -# taccsite_cms/settings.py """ -Django settings for taccsite_cms project. - +Django settings Generated by 'django-admin startproject' using Django 1.11.22. - For more information on this file, see https://docs.djangoproject.com/en/2.2/topics/settings/ - For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/ref/settings/ """ @@ -15,91 +11,171 @@ import logging import os from glob import glob +import ldap +from django_auth_ldap.config import LDAPSearch, GroupOfNamesType - +SECRET_KEY = 'CHANGE_ME' def gettext(s): return s +DATA_DIR = os.path.dirname(os.path.dirname(__file__)) + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -# Import secret values dynamically without breaking portal. -def getsecrets(): - new_secrets = {}; # Var to hold secret values once imported succesfully. - # Check for production secrets. - try: - print('Checking for secret production values') - import taccsite_cms.secrets as secrets # Prod/Staging/Local Dev values (used instead of the default values if present) - new_secrets = secrets - print('Production secrets found, using values') - except ModuleNotFoundError as err: - # Error handling - print(err) - print('No production secrets found') - pass - # Check for the default secret values. - try: - print('Checking for default secret values') - import taccsite_cms.default_secrets as default_secrets # Default demo values (works for basic local dev out of the box) - new_secrets = default_secrets - print('Default secrets found, using default values') - except ModuleNotFoundError as err: - # Error handling - print(err) - print('No default secrets found') - print('Check that you have a secrets.py or default_secrets.py') - finally: - # Return the secret values if they are found. - return new_secrets - -# Assign secret settings values. -current_secrets = getsecrets() - -# Boolean check to turn on/off console logging statements. -CONSOLE_LOG_ENABLED = current_secrets._CONSOLE_LOG_ENABLED - -# Verifying console logging is on. -if CONSOLE_LOG_ENABLED: - print("--> Variable CONSOLE_LOG_ENABLED: ", CONSOLE_LOG_ENABLED) - -LDAP_ENABLED = current_secrets._LDAP_ENABLED - -if CONSOLE_LOG_ENABLED: - print("--> Variable LDAP_ENABLED: ", LDAP_ENABLED) - -if LDAP_ENABLED: - import ldap - from django_auth_ldap.config \ - import LDAPSearch, GroupOfNamesType +DEBUG = True # False for Prod. -DATA_DIR = os.path.dirname(os.path.dirname(__file__)) +# Specify allowed hosts or use an asterisk to allow any host and simplify the config. +# ALLOWED_HOSTS = ['hostname.tacc.utexas.edu', 'host.ip.v4.address', '0.0.0.0', 'localhost', '127.0.0.1'] # In production. +ALLOWED_HOSTS = ['0.0.0.0', '127.0.0.1', 'localhost', '*'] # In development. -if CONSOLE_LOG_ENABLED: - print("--> Variable DATA_DIR: ", DATA_DIR) +# Requires django-auth-ldap ≥ 2.0.0 +LDAP_ENABLED = True -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +######################## +# DATABASE SETTINGS +######################## + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'PORT': '5432', + 'NAME': 'taccsite', + 'USER': 'postgresadmin', + 'PASSWORD': 'taccforever', # Change before live deployment. + 'HOST': 'core_cms_postgres' + } +} + +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "taccsite_cms.remote_cms_auth.CorePortalAuthBackend", + "django_auth_ldap.backend.LDAPBackend" +] -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ +''' LDAP Auth Settings ''' +AUTH_LDAP_SERVER_URI = "ldap://ldap.tacc.utexas.edu" +AUTH_LDAP_CONNECTION_OPTIONS = {ldap.OPT_REFERRALS: 0} +AUTH_LDAP_START_TLS = True +AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True +AUTH_LDAP_BIND_DN = "" +AUTH_LDAP_BIND_PASSWORD = "" +AUTH_LDAP_AUTHORIZE_ALL_USERS = True + +AUTH_LDAP_USER_SEARCH = LDAPSearch( + "ou=People,dc=tacc,dc=utexas,dc=edu", ldap.SCOPE_SUBTREE, "(uid=%(user)s)" +) + +AUTH_LDAP_USER_ATTR_MAP = { + "first_name": "givenName", + "last_name": "sn", + "email": "mail", +} + +SITE_ID = 1 + +CMS_TEMPLATES = ( + ('standard.html', 'Standard'), + ('fullwidth.html', 'Full Width'), +) + +CMS_PERMISSION = True + +######################## +# GOOGLE ANALYTICS +######################## + +# To use during dev, Tracking Protection in browser needs to be turned OFF. +GOOGLE_ANALYTICS_PROPERTY_ID = "UA-123ABC@%$&-#" +GOOGLE_ANALYTICS_PRELOAD = True + +######################## +# CMS FORMS +######################## + +# Create CMS Forms +# SEE: https://pypi.org/project/djangocms-forms/ +# SEE: https://www.google.com/recaptcha/admin/create +DJANGOCMS_FORMS_RECAPTCHA_PUBLIC_KEY = "" +DJANGOCMS_FORMS_RECAPTCHA_SECRET_KEY = "" + +######################## +# ELASTICSEARCH +######################## + +ES_AUTH = 'username:password' +ES_HOSTS = 'http://elasticsearch:9200' +ES_INDEX_PREFIX = 'cms-dev-{}' +ES_DOMAIN = 'http://localhost:8000' + +""" +Optional theming of CMS (certain themes may only affect some elements) +Usage: +- None (standard theme) +- 'has-dark-logo' +""" +THEME = None + +TACC_BRANDING = [ + "tacc", + "site_cms/img/org_logos/tacc-white.png", + "branding-tacc", + "https://www.tacc.utexas.edu/", + "_blank", + "TACC Logo", + "anonymous", + "True" +] + +UTEXAS_BRANDING = [ + "utexas", + "site_cms/img/org_logos/utaustin-white.png", + "branding-utaustin", + "https://www.utexas.edu/", + "_blank", + "University of Texas at Austin Logo", + "anonymous", + "True" +] + +NSF_BRANDING = [ + "nsf", + "site_cms/img/org_logos/nsf-white.png", + "branding-nsf", + "https://www.nsf.gov/", + "_blank", + "NSF Logo", + "anonymous", + "True" +] -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = current_secrets._SECRET_KEY +BRANDING = [ TACC_BRANDING, UTEXAS_BRANDING ] + +LOGO = [ + "portal", + "site_cms/img/org_logos/portal.png", + "", + "/", + "_self", + "Portal Logo", + "anonymous", + "True" +] -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = current_secrets._DEBUG +FAVICON = { + "img_file_src": "site_cms/img/favicons/favicon.ico" +} -# Host Access. -ALLOWED_HOSTS = current_secrets._ALLOWED_HOSTS +INCLUDES_CORE_PORTAL = True -# Custom Branding. -THEME = current_secrets._THEME if hasattr(current_secrets, '_THEME') else None -BRANDING = current_secrets._BRANDING -LOGO = current_secrets._LOGO -FAVICON = current_secrets._FAVICON +LOGOUT_REDIRECT_URL='/' -# Configure Portal. -PORTAL = current_secrets._PORTAL +## using container name to avoid cep.dev dns issues locally +## this will need to be updated for dev/pprd/prod systems +## for example, CEP_AUTH_VERIFICATION_ENDPOINT=https://dev.cep.tacc.utexas.edu +CEP_AUTH_VERIFICATION_ENDPOINT='http://django:6000' -# Optional features. -FEATURES = current_secrets._FEATURES +######################## +# CLIENT BUILD SETTINGS +######################## # Application definition ROOT_URLCONF = 'taccsite_cms.urls' @@ -109,9 +185,6 @@ def getsecrets(): STATIC_URL = '/static/' STATIC_ROOT = os.path.join(DATA_DIR, 'static') -if CONSOLE_LOG_ENABLED: - print("--> Variable STATIC_ROOT: ", STATIC_ROOT) - STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'taccsite_cms', 'static'), # os.path.join(BASE_DIR, 'taccsite_cms', 'en', 'static'), @@ -119,16 +192,10 @@ def getsecrets(): os.path.join(BASE_DIR, 'taccsite_custom', '*', 'static') )) -if CONSOLE_LOG_ENABLED: - print("--> Variable STATICFILES_DIRS: ", STATICFILES_DIRS) - # User Uploaded Files Location. MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(DATA_DIR, 'media') -if CONSOLE_LOG_ENABLED: - print("--> Variable MEDIA_ROOT: ", MEDIA_ROOT) - TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -139,7 +206,6 @@ def getsecrets(): ) + [ os.path.join(BASE_DIR, 'taccsite_cms', 'templates') ], - # 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.contrib.auth.context_processors.auth', @@ -169,9 +235,6 @@ def getsecrets(): }, ] -if CONSOLE_LOG_ENABLED: - print("--> Variable TEMPLATES: ", TEMPLATES) - MIDDLEWARE = [ 'cms.middleware.utils.ApphookReloadMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -207,10 +270,8 @@ def getsecrets(): 'djangocms_text_ckeditor', 'filer', 'easy_thumbnails', - # 'djangocms_audio', 'djangocms_column', 'djangocms_file', - # 'djangocms_forms', # FP-416: Pending full support 'djangocms_link', 'djangocms_picture', 'djangocms_style', @@ -235,15 +296,10 @@ def getsecrets(): 'djangocms_bootstrap4.contrib.bootstrap4_utilities', 'haystack', 'aldryn_apphooks_config', - # For faster testing, disable migrations during database creation - # SEE: https://stackoverflow.com/a/37150997 'test_without_migrations', 'taccsite_cms', - # Restore djangocms plugins that bootstrap4 hides 'taccsite_cms.contrib.bootstrap4_djangocms_link', 'taccsite_cms.contrib.bootstrap4_djangocms_picture', - # TODO: Extract TACC CMS UI components into pip-installable plugins - # FAQ: The djangocms_bootstrap4 library can serve as an example 'taccsite_cms.contrib.taccsite_sample', 'taccsite_cms.contrib.taccsite_system_monitor', 'taccsite_cms.contrib.taccsite_data_list' @@ -267,80 +323,17 @@ def get_subdirs_as_module_names(path): # Append CMS project paths as module names to INSTALLED_APPS # FAQ: This automatically looks into `/taccsite_custom` and creates an "App" for each directory within CUSTOM_CMS_DIR = os.path.join(BASE_DIR, 'taccsite_custom') + INSTALLED_APPS_APPEND = get_subdirs_as_module_names(CUSTOM_CMS_DIR) INSTALLED_APPS = INSTALLED_APPS + INSTALLED_APPS_APPEND - -if CONSOLE_LOG_ENABLED: - print("--> Variable INSTALLED_APPS: ", INSTALLED_APPS) - -AUTHENTICATION_BACKENDS = [ - "django.contrib.auth.backends.ModelBackend", -] - -if LDAP_ENABLED: - AUTHENTICATION_BACKENDS.insert(0, - "django_auth_ldap.backend.LDAPBackend" - ) - - ''' LDAP Auth Settings ''' - AUTH_LDAP_SERVER_URI = "ldap://ldap.tacc.utexas.edu" - AUTH_LDAP_CONNECTION_OPTIONS = {ldap.OPT_REFERRALS: 0} - AUTH_LDAP_START_TLS = True - AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True - - AUTH_LDAP_BIND_DN = "" - AUTH_LDAP_BIND_PASSWORD = "" - AUTH_LDAP_USER_SEARCH = LDAPSearch( - "ou=People,dc=tacc,dc=utexas,dc=edu", ldap.SCOPE_SUBTREE, "(uid=%(user)s)" - ) - - AUTH_LDAP_AUTHORIZE_ALL_USERS = True - - AUTH_LDAP_USER_ATTR_MAP = { - "first_name": "givenName", - "last_name": "sn", - "email": "mail", - } - - ''' - # More customizations - - AUTH_LDAP_REQUIRE_GROUP = "cn=TACC-ACI-WMA,ou=Groups,dc=tacc,dc=utexas,dc=edu" - AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - "ou=Groups,dc=tacc,dc=utexas,dc=edu", - ldap.SCOPE_SUBTREE, - "(objectClass=groupOfUniqueNames)", - ) - AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn") - ''' - ''' End LDAP Auth Settings ''' - -if getattr(current_secrets, '_CACHES', None): - CACHES = secrets._CACHES # Are we actually using this setting? - -DATABASES = current_secrets._DATABASES - MIGRATION_MODULES = { } - -# SSL Setup. -# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -# SECURE_SSL_REDIRECT = True -# SESSION_COOKIE_SECURE = True -# CSRF_COOKIE_SECURE = True - -# Internationalization -# https://docs.djangoproject.com/en/2.2/topics/i18n/ - LANGUAGE_CODE = 'en' TIME_ZONE = 'America/Chicago' USE_I18N = True USE_L10N = True USE_TZ = True -# DjangoCMS Setup. -SITE_ID = current_secrets._SITE_ID - LANGUAGES = ( # Customize this ('en', gettext('en')), @@ -364,7 +357,6 @@ def get_subdirs_as_module_names(path): }, } -CMS_TEMPLATES = current_secrets._CMS_TEMPLATES CMS_PERMISSION = True CMS_PLACEHOLDER_CONF = {} @@ -376,52 +368,6 @@ def get_subdirs_as_module_names(path): 'easy_thumbnails.processors.filters' ) - -# FEATURES. -if CONSOLE_LOG_ENABLED: - print("--> Variable FEATURES: ") - for feature in FEATURES: - print(feature + ": ", FEATURES[feature]) - -if current_secrets._FEATURES['blog']: - # Install required apps - INSTALLED_APPS += [ - # Blog/News - # 'filer', # Already added - # 'easy_thumbnails', # Already added - 'parler', - 'taggit', - 'taggit_autosuggest', - 'meta', # also supports `djangocms_page_meta` - 'sortedm2m', - 'djangocms_blog', - - # Metadata - 'djangocms_page_meta', - ] - - # Metadata: Configure - META_SITE_PROTOCOL = 'http' - META_USE_SITES = True - META_USE_OG_PROPERTIES = True - META_USE_TWITTER_PROPERTIES = True - META_USE_GOOGLEPLUS_PROPERTIES = True # django-meta 1.x+ - # META_USE_SCHEMAORG_PROPERTIES=True # django-meta 2.x+ - - # Blog/News: Set custom paths for templates - BLOG_PLUGIN_TEMPLATE_FOLDERS = ( - ('plugins/default', 'Default template'), # i.e. `templates/djangocms_blog/plugins/default/` - ('plugins/default-clone', 'Clone of default template'), # i.e. `templates/djangocms_blog/plugins/default-clone/` - ) - - # Blog/News: Change default values for the auto-setup of one `BlogConfig` - # SEE: https://github.com/nephila/djangocms-blog/issues/629 - BLOG_AUTO_SETUP = True - BLOG_AUTO_HOME_TITLE ='Home' - BLOG_AUTO_BLOG_TITLE = 'News' - BLOG_AUTO_APP_TITLE = 'News' - - DJANGOCMS_PICTURE_NESTING = True DJANGOCMS_PICTURE_RESPONSIVE_IMAGES = True DJANGOCMS_PICTURE_RESPONSIVE_IMAGES_VIEWPORT_BREAKPOINTS = [ @@ -437,39 +383,19 @@ def get_subdirs_as_module_names(path): FILE_UPLOAD_PERMISSIONS = 0o644 FILE_UPLOAD_MAX_MEMORY_SIZE = 20000000 # 20MB -# Custom picture templates - if required. -# DJANGOCMS_PICTURE_TEMPLATES = [ -# ('background', _('Background image')), # Need to design these first! -# ] - DJANGOCMS_AUDIO_ALLOWED_EXTENSIONS = ['mp3', 'ogg', 'wav'] -# Custom audio templates - if required. -# DJANGOCMS_AUDIO_TEMPLATES = [ -# ('feature', _('Featured Version')), -# ] # Djangocms Forms Settings. # SEE: https://github.com/mishbahr/djangocms-forms#configuration DJANGOCMS_FORMS_PLUGIN_MODULE = ('Generic') DJANGOCMS_FORMS_PLUGIN_NAME = ('Form') -# DJANGOCMS_FORMS_DEFAULT_TEMPLATE = 'djangocms_forms/form_template/default.html' + DJANGOCMS_FORMS_TEMPLATES = ( ('djangocms_forms/form_template/default.html', ('Default')), ) DJANGOCMS_FORMS_USE_HTML5_REQUIRED = False -# DJANGOCMS_FORMS_WIDGET_CSS_CLASSES = {'__all__': ('form-control', ) } -DJANGOCMS_FORMS_REDIRECT_DELAY = 10000 # 10 seconds -DJANGOCMS_FORMS_RECAPTCHA_PUBLIC_KEY = current_secrets._DJANGOCMS_FORMS_RECAPTCHA_PUBLIC_KEY -DJANGOCMS_FORMS_RECAPTCHA_SECRET_KEY = current_secrets._DJANGOCMS_FORMS_RECAPTCHA_SECRET_KEY - -# Google Analytics. -GOOGLE_ANALYTICS_PROPERTY_ID = current_secrets._GOOGLE_ANALYTICS_PROPERTY_ID -GOOGLE_ANALYTICS_PRELOAD = current_secrets._GOOGLE_ANALYTICS_PRELOAD - -# SETTINGS VARIABLE EXPORTS. -# Use a custom namespace (using default settings.VARIABLE configuration) -SETTINGS_EXPORT_VARIABLE_NAME = 'settings' +DJANGOCMS_FORMS_REDIRECT_DELAY = 1 # Elasticsearch Indexing HAYSTACK_ROUTERS = ['aldryn_search.router.LanguageRouter',] @@ -479,28 +405,42 @@ def get_subdirs_as_module_names(path): HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine', - 'URL': current_secrets._ES_HOSTS, - 'INDEX_NAME': current_secrets._ES_INDEX_PREFIX.format('cms'), - 'KWARGS': {'http_auth': current_secrets._ES_AUTH } + 'URL': ES_HOSTS, + 'INDEX_NAME': 'cms', + 'KWARGS': {'http_auth': ES_AUTH } } } -ES_DOMAIN = current_secrets._ES_DOMAIN +SETTINGS_EXPORT_VARIABLE_NAME = 'settings' + +FEATURES = '' + +try: + from taccsite_cms.settings_custom import * +except: + None + # do nothing + +try: + from taccsite_cms.secrets import * +except: + None + # do nothing + +try: + from taccsite_cms.settings_local import * +except: + None + # do nothing -# Exported settings. SETTINGS_EXPORT = [ 'DEBUG', + 'FEATURES', 'THEME', 'BRANDING', 'LOGO', 'FAVICON', - 'PORTAL', - 'FEATURES', + 'INCLUDES_CORE_PORTAL', 'GOOGLE_ANALYTICS_PROPERTY_ID', 'GOOGLE_ANALYTICS_PRELOAD' ] - -if CONSOLE_LOG_ENABLED: - print("--> Variable SETTINGS_EXPORT: ") - for setting in SETTINGS_EXPORT: - print(setting) diff --git a/taccsite_cms/settings_custom.example.py b/taccsite_cms/settings_custom.example.py new file mode 100644 index 000000000..6ea8dac74 --- /dev/null +++ b/taccsite_cms/settings_custom.example.py @@ -0,0 +1,43 @@ + +''' + +settings_custom.py file can be used to override default values in settings.py + +The settings_custom.py file is loaded after default settings but before settings assignment is complete, +giving us the opportunity to override settings in either settings_custom.py (usually set in custom sites) +or in settings_local.py (usually used in a local dev environment). + +To override a setting, +simply copy/paste the default settings and set the new/custom values + +If a setting override is for a custom site in an existing submodule, the change is made in +the settings_custom.py file of that submodules site directory and (if not a secret) can be committed + + +Unless modifying default behavior for the default cms and all custom sites (which probably means that +setting should be in the settings.py file), the settings_custom.py file that should be modified is +the one in the appropriate taccsite_custom site directory. + +''' + +# For example if we want to change LDAP auth settings for a custome site, let's say frontera-cms +# we simply copy the setting from settings.py, and +# assign the new value in "Core-CMS/taccsite_custom/frontera-cms/settings_custom.py" +''' Example LDAP Auth Setting change where we just changing the ldap url ''' +AUTH_LDAP_SERVER_URI = "ldap://cluster.ldap.tacc.utexas.edu" + +#The same goes for other more commonly customized values like below +# A customization to this default would be applied in settings_custom.py of the appropriate custom site +PORTAL_LOGO = [ + "portal", + "site_cms/img/org_logos/portal.png", + "", + "/", + "_self", + "Portal Logo", + "anonymous", + "True" +] + +LOGO = PORTAL_LOGO + diff --git a/taccsite_cms/settings_to_json.py b/taccsite_cms/settings_to_json.py index 19935ebd9..b99776e76 100644 --- a/taccsite_cms/settings_to_json.py +++ b/taccsite_cms/settings_to_json.py @@ -14,7 +14,7 @@ # Create JSON setting_names = ["THEME"] settings_export = {} -# FAQ: The print() statements in settings.py would corrupt the JSON +# FAQ: Any print() statements in settings.py would corrupt the JSON with suppress_stdout(): for setting_name in setting_names: settings_export[setting_name] = getattr(settings, setting_name) diff --git a/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-header.css b/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-header.css index cf1ed70b1..5d2f24524 100644 --- a/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-header.css +++ b/taccsite_cms/static/site_cms/css/src/_imports/trumps/s-header.css @@ -228,6 +228,10 @@ Styleguide Trumps.Scopes.Header color: var(--global-color-primary--xx-light); background-color: var(--global-color-primary--xx-dark); + + /* !!!: Missing env() var `--header-major-border-color` */ + /* To hide border, set this in theme to match `--header-bkgd-color` value */ + /* border-bottom: 1px solid env(--header-major-border-color); */ border-bottom: 1px solid var(--global-color-primary--xx-light); } diff --git a/taccsite_cms/static/site_cms/css/src/_themes/TODO.md b/taccsite_cms/static/site_cms/css/src/_themes/TODO.md index 59610f835..999cbd85f 100644 --- a/taccsite_cms/static/site_cms/css/src/_themes/TODO.md +++ b/taccsite_cms/static/site_cms/css/src/_themes/TODO.md @@ -1,4 +1,4 @@ # TACC CMS - Stylesheets - Themes - Migrate `/taccsite_cms/site_shared/css/src/_imports/settings/*` to theme data. -- Support one theme extending another theme (the default or another). +- Support one theme extending another theme (default theme or another theme). diff --git a/taccsite_cms/templates/assets_site_delayed.html b/taccsite_cms/templates/assets_site_delayed.html index a7392c21b..6d9df5ff3 100644 --- a/taccsite_cms/templates/assets_site_delayed.html +++ b/taccsite_cms/templates/assets_site_delayed.html @@ -18,7 +18,7 @@ {# FAQ: Not loaded in `assets_font.html` because these is NOT font for content which should avoid FOUT, FOIT, FOFT; but decorative, thus superfluous, icons #} -{% if settings.PORTAL %} - +{% if settings.INCLUDES_CORE_PORTAL %} + {% endif %} diff --git a/taccsite_cms/templates/header.html b/taccsite_cms/templates/header.html index e457ad0e8..32c4c079c 100644 --- a/taccsite_cms/templates/header.html +++ b/taccsite_cms/templates/header.html @@ -9,7 +9,7 @@ {# NOTE: `navbar-expand-*` maps to a `media-queries.css`'s `--nav-*` value #} {# CAVEAT: The mobile nav CSS can NOT support conditional `navbar-expand-*` #} -