Commit 05920eef authored by frekk's avatar frekk

Merge branch 'master' into frekk-testing

parents 09dbcd6b 8aa4409e
stages:
- test
- deploy
variables:
DB_ENGINE : django.db.backends.sqlite3
before_script:
- echo "preparing environment"
- virtualenv env -p `which python3`
- . env/bin/activate
- pip install -r pip-packages.txt
run_tests:
stage: test
script:
- echo "Running tests"
- envsubst < src/gms/settings_local.example.py > src/gms/settings_local.py
- python src/manage.py check
deploy_testing:
stage: deploy
tags:
- test
variables:
DB_NAME : /var/www/test/$CI_COMMIT_REF_NAME/.db/members.db
DEPLOY_HOST : $CI_COMMIT_REF_NAME.test.ucc.asn.au
SHORT_ENV_NAME : TEST
script:
- echo "Deploy to test"
- rm -f /var/www/test/$CI_COMMIT_REF_NAME
- rm -f /etc/uwsgi/vassals/$CI_COMMIT_REF_NAME.ini
- mkdir .db
- envsubst < src/gms/settings_local.example.py > src/gms/settings_local.py
- chmod 600 src/gms/settings_local.py
- ln -srT ./ /var/www/test/$CI_COMMIT_REF_NAME
- python src/manage.py collectstatic
- python src/manage.py makemigrations
- python src/manage.py migrate --run-syncdb
- ln -s /etc/uwsgi/vassals/test.skel /etc/uwsgi/vassals/$CI_COMMIT_REF_NAME.ini
environment:
name: test/$CI_COMMIT_REF_NAME
url: https://$CI_COMMIT_REF_NAME.test.ucc.asn.au
on_stop: stop_testing
except:
- master
deploy_staging:
stage: deploy
tags:
- stage
variables:
DB_NAME : /var/www/stage/.db/members.db
DEPLOY_HOST : stage.test.ucc.asn.au
SHORT_ENV_NAME : STAGE
script:
- echo "Deploy to staging"
- rm -f /services/$CI_PROJECT_NAME
- rm -f /etc/uwsgi/vassals/stage.ini
- ln -srT ./ /services/$CI_PROJECT_NAME
- envsubst < src/gms/settings_local.example.py > src/gms/settings_local.py
- chmod 600 src/gms/settings_local.py
- python src/manage.py collectstatic
- python src/manage.py makemigrations
- python src/manage.py migrate --run-syncdb
- ln -s /etc/uwsgi/vassals/stage.skel /etc/uwsgi/vassals/stage.ini
environment:
name: stage
url: https://stage.test.ucc.asn.au
only:
- master
stop_testing:
stage: deploy
variables:
GIT_STRATEGY: none
when: manual
environment:
name: test/$CI_COMMIT_REF_NAME
action: stop
except:
- master
script:
- rm -f /var/www/test/$CI_COMMIT_REF_NAME
- rm -f /etc/uwsgi/vassals/$CI_COMMIT_REF_NAME.ini
certifi==2018.11.29
Django==2.1.5
django-auth-ldap==1.7.0
django-formtools==2.1
django-sslserver==0.20
psycopg2-binary==2.7.6.1
pyasn1==0.4.4
......@@ -11,3 +12,4 @@ pytz==2018.7
six==1.12.0
squareconnect==2.20181212.0
urllib3==1.24.1
ldap3==2.5.2
default_app_config = 'gms.apps.GmsConfig'
from django.conf import settings
def global_settings(request):
return {
'DEPLOYMENT_ENV' : settings.ENV
}
......@@ -14,26 +14,27 @@ from gms.settings_local import *
# Application definition
INSTALLED_APPS = (
'sslserver',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'memberdb',
'import_members',
'squarepay',
'sslserver',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'memberdb',
'import_members',
'squarepay',
'formtools',
)
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'memberdb.views.MemberMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'memberdb.views.MemberMiddleware',
]
ROOT_URLCONF = 'gms.urls'
......@@ -63,33 +64,34 @@ DATABASE_ROUTERS = ['import_members.db.MemberDbRouter']
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'static'),
]
STATIC_URL = '/media/'
STATIC_ROOT = os.path.join(ROOT_DIR, 'media')
AUTHENTICATION_BACKENDS = [
# see https://django-auth-ldap.readthedocs.io/en/latest for configuration info
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
# see https://django-auth-ldap.readthedocs.io/en/latest for configuration info
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
]
# see settings_local.py for LDAP settings
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(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',
],
},
},
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(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',
'gms.context_processors.global_settings'
],
},
},
]
TEMPLATE_DEBUG = DEBUG
......@@ -100,51 +102,51 @@ MESSAGE_LEVEL = message_constants.DEBUG
### Logging configuration ###
import logging
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
},
'handlers': {
'logfile': {
'level': LOG_LEVEL,
'class':'logging.handlers.RotatingFileHandler',
'filename': LOG_FILENAME,
'maxBytes': 500000,
'backupCount': 2,
'formatter': 'standard',
},
'console':{
'level': LOG_LEVEL,
'class':'logging.StreamHandler',
'formatter': 'standard'
},
},
'loggers': {
'django': {
'handlers':['logfile', 'console'],
'propagate': True,
'level': LOG_LEVEL,
},
'django.db.backends': {
'handlers': ['logfile', 'console'],
'level': LOG_LEVEL,
'propagate': False,
},
'django.contrib.auth': {
'handlers': ['logfile', 'console'],
'level': LOG_LEVEL,
},
'django_auth_ldap': {
'level': LOG_LEVEL,
'handlers': ['logfile', 'console'],
},
'squarepay': {
'level': LOG_LEVEL,
'handlers': ['logfile', 'console'],
}
},
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
},
'handlers': {
'logfile': {
'level': LOG_LEVEL,
'class':'logging.handlers.RotatingFileHandler',
'filename': LOG_FILENAME,
'maxBytes': 500000,
'backupCount': 2,
'formatter': 'standard',
},
'console':{
'level': LOG_LEVEL,
'class':'logging.StreamHandler',
'formatter': 'standard'
},
},
'loggers': {
'django': {
'handlers':['logfile', 'console'],
'propagate': True,
'level': LOG_LEVEL,
},
'django.db.backends': {
'handlers': ['logfile', 'console'],
'level': LOG_LEVEL,
'propagate': False,
},
'django.contrib.auth': {
'handlers': ['logfile', 'console'],
'level': LOG_LEVEL,
},
'django_auth_ldap': {
'level': LOG_LEVEL,
'handlers': ['logfile', 'console'],
},
'squarepay': {
'level': LOG_LEVEL,
'handlers': ['logfile', 'console'],
}
},
}
......@@ -7,36 +7,38 @@ ROOT_DIR = os.path.dirname(BASE_DIR)
DEBUG = True
ENV = '${SHORT_ENV_NAME}'
ADMINS = (
('UCC Committee', '[email protected]'),
('UCC Committee', '[email protected]'),
)
### Database connection options ###
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
# this should end up in uccportal/.db/members.db
'NAME': os.path.join(ROOT_DIR, '.db', 'members.db'), # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
},
'memberdb_old': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'uccmemberdb_2018',
'USER': 'uccmemberdb',
'PASSWORD': 'something-secret-here',
'HOST': 'mussel.ucc.gu.uwa.edu.au',
'PORT': '',
}
'default': {
'ENGINE': '${DB_ENGINE}', # Add 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
# this should end up in uccportal/.db/members.db
'NAME': '${DB_NAME}', # Or path to database file if using sqlite3.
'USER': '${DB_USER}', # Not used with sqlite3.
'PASSWORD': '${DB_SECRET}', # Not used with sqlite3.
'HOST': '${DB_HOST}', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
},
'memberdb_old': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'uccmemberdb_2018',
'USER': 'uccmemberdb',
'PASSWORD': '${OLDDB_SECRET}',
'HOST': 'mussel.ucc.gu.uwa.edu.au',
'PORT': '',
}
}
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'something-unique-here'
SECRET_KEY = '${APP_SECRET}'
# Set this to whatever your ServerName/ServerAlias(es) are
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['${DEPLOY_HOST}']
LOG_LEVEL = 'DEBUG'
LOG_FILENAME = os.path.join(ROOT_DIR, "django.log")
......@@ -44,13 +46,19 @@ LOG_FILENAME = os.path.join(ROOT_DIR, "django.log")
import ldap
from django_auth_ldap.config import LDAPSearch, ActiveDirectoryGroupType, LDAPGroupQuery
# LDAP admin settings
LDAP_BASE_DN = 'DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au'
LDAP_USER_SEARCH_DN = 'CN=Users,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au'
LDAP_BIND_DN = 'CN=uccportal,CN=Users,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au'
LDAP_BIND_SECRET = "${LDAP_SECRET}"
# this could be ad.ucc.gu.uwa.edu.au but that doesn't resolve externally -
# useful for testing, but should be changed in production so failover works
AUTH_LDAP_SERVER_URI = 'ldaps://samson.ucc.gu.uwa.edu.au/'
AUTH_LDAP_SERVER_URI = 'ldaps://ad.ucc.gu.uwa.edu.au'
# This is also a bad idea, should be changed in production
AUTH_LDAP_GLOBAL_OPTIONS = {
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
}
# directly attempt to authenticate users to bind to LDAP
......@@ -63,33 +71,33 @@ AUTH_LDAP_FIND_GROUP_PERMS = False
AUTH_LDAP_USER_DN_TEMPLATE = 'CN=%(user)s,CN=Users,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au'
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au",
ldap.SCOPE_SUBTREE, "(objectClass=group)")
ldap.SCOPE_SUBTREE, "(objectClass=group)")
# Populate the Django user from the LDAP directory.
# note: somehow the LDAP/AD users don't have firstName/sn, rather the full name is in name or displayName
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "email",
"first_name": "givenName",
"last_name": "sn",
"email": "email",
}
ADMIN_ACCESS_QUERY = \
LDAPGroupQuery("CN=committee,OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au") | \
LDAPGroupQuery("CN=door,OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au") | \
LDAPGroupQuery("CN=wheel,OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au")
LDAPGroupQuery("CN=committee,OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au") | \
LDAPGroupQuery("CN=door,OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au") | \
LDAPGroupQuery("CN=wheel,OU=Groups,DC=ad,DC=ucc,DC=gu,DC=uwa,DC=edu,DC=au")
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
# staff can login to the admin site
"is_staff": ADMIN_ACCESS_QUERY,
# staff can login to the admin site
"is_staff": ADMIN_ACCESS_QUERY,
# superusers have all permissions (but also need staff to login to admin site)
"is_superuser": ADMIN_ACCESS_QUERY,
# superusers have all permissions (but also need staff to login to admin site)
"is_superuser": ADMIN_ACCESS_QUERY,
}
# the Square app and location data (set to sandbox unless you want it to charge people)
SQUARE_APP_ID = 'maybe-sandbox-something-something-here'
SQUARE_LOCATION = 'CBASEDE-this-is-probably-somewhere-in-Sydney'
SQUARE_ACCESS_TOKEN = 'keep-this-very-secret'
SQUARE_APP_ID = '${SQUARE_APP_ID}'
SQUARE_LOCATION = '${SQUARE_LOCATION}'
SQUARE_ACCESS_TOKEN = '${SQUARE_SECRET}'
DISPENSE_BIN = '/usr/local/bin/dispense'
......@@ -98,4 +106,4 @@ EMAIL_HOST = "secure.ucc.asn.au"
EMAIL_PORT = 465
EMAIL_USE_SSL = True
EMAIL_HOST_USER = "uccportal"
EMAIL_HOST_PASSWORD = "changeme"
EMAIL_HOST_PASSWORD = "${EMAIL_SECRET}"
wsgi.wsgi
\ No newline at end of file
"""
WSGI config for gms project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gms.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
from django.contrib import messages
from django import forms
from formtools.wizard.views import SessionWizardView
from .models import Member
from .forms import MyModelForm, MyForm
from .views import MyUpdateView, MyWizardView
from memberdb.account_backend import validate_username, create_ad_user
class AccountForm(MyModelForm):
# form fields
username = forms.SlugField(
validators=[validate_username],
max_length=19,
)
password = forms.CharField(
min_length=10,
max_length=127,
widget=forms.PasswordInput,
strip=False,
help_text="Password must be between 10 and 127 characters long"
)
confirm_password = forms.CharField(
min_length=10,
max_length=127,
widget=forms.PasswordInput,
strip=False,
)
class Meta:
model = Member
fields = ['username']
def clean(self):
try:
if (self['password'].value() != self['confirm_password'].value()):
self.add_error('confirm_password', 'Passwords must match.')
except:
pass
super().clean();
class EmailForm(MyModelForm):
forward = forms.BooleanField(required=False)
email_address = forms.EmailField(
label='Forwarding address (optional)',
required=False,
help_text="Your club email will be forwarded to this address. Leave blank if email forwarding is not required"
)
class Meta:
model = Member
fields = ['forward', 'email_address']
def clean(self):
if self['forward'].value() == True:
try:
if (len(self['email_address'].value()) == 0):
self.add_error('email_address', 'Email field cannot be left blank.')
if (self['email_address'].value().split('@')[1] in ["ucc.asn.au", "ucc.gu.uwa.edu.au"]):
self.add_error('email_address', 'Forwarding address cannot be the same as your account address.')
except:
pass
super().clean();
class DispenseForm(MyForm):
pin = forms.CharField(
min_length=0,
max_length=4,
widget=forms.PasswordInput,
strip=False,
required=False,
help_text="PIN must be 4 digits long")
confirm_pin = forms.CharField(
min_length=0,
max_length=4,
widget=forms.PasswordInput,
required=False,
strip=False,
)
def clean(self):
try:
if len(self['pin'].value()) != 4 :
self.add_error('pin', 'PIN must be excatly 4 digits.')
if not self['pin'].value().isdigit():
self.add_error('pin', 'PIN can only contain numbers.')
if (self['pin'].value() != self['confirm_pin'].value()):
self.add_error('confirm_pin', 'PINs must match.')
except:
pass
super().clean();
class AccountView(MyWizardView):
form_list = [AccountForm,EmailForm,DispenseForm]
template_name = 'admin/memberdb/account_create.html'
admin = None
def get_form_instance(self, step):
return self.object
def get_context_data(self, **kwargs):
m = self.object
context = super().get_context_data(**kwargs)
context.update(self.admin.admin_site.each_context(self.request))
context.update({
'opts': self.admin.model._meta,
'member': m,
})
return context
def done(self, form_list, form_dict, **kwargs):
# create the user and save their username if successfull
try:
if create_ad_user(self.get_cleaned_data_for_step('0'), self.object):
form_dict['0'].save()
make_home(self.get_cleaned_data_for_step('1'), self.object)
make_dispense_account(self.object.username, self.get_cleaned_data_for_step('2')['pin'])
subscribe_to_list(self.object)
except Exception as e:
messages.error(self.request,'Account creation failed for %s', self.object)
messages.error(self.request, e)
raise #DEBUG
else:
messages.success(self.request, 'An account has been successfully created for %s.' % self.object)
return HttpResponseRedirect(reverse("admin:memberdb_membership_changelist"))
#return accountProgressView(self.request, m)
def accountProgressView(request, member):
return
def accountFinalView():
return render(request, 'accountfinal.html', context)
import logging
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.translation import gettext_lazy as _