Skip to content

Commit

Permalink
Merge pull request #111 from uw-it-aca/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jlaney authored Aug 28, 2020
2 parents 437cc85 + 9439ed0 commit 862adeb
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 83 deletions.
164 changes: 88 additions & 76 deletions project/base_settings/auth_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,94 +3,106 @@
from .common import INSTALLED_APPS, MIDDLEWARE, AUTHENTICATION_BACKENDS
from .setting_utils import parse_bool_from_str

for auth in os.getenv('AUTH', '').split(' '):
if auth.startswith('SAML') and 'uw_saml' not in INSTALLED_APPS:
INSTALLED_APPS.append('uw_saml')

LOGIN_URL = '/saml/login'
LOGOUT_URL = '/saml/logout'
SAML_USER_ATTRIBUTE = os.getenv('SAML_USER_ATTRIBUTE', 'uwnetid')
SAML_FORCE_AUTHN = parse_bool_from_str(os.getenv('SAML_FORCE_AUTHN', 'False'))
def auth_from_env(auth):
return next((a for a in os.getenv('AUTH', '').split(' ') if a.startswith(auth)), None)

if auth == 'SAML_MOCK' or auth == 'SAML_DJANGO_LOGIN':
DEFAULT_SAML_ATTRIBUTES = {
'uwnetid': ['javerage'],
'affiliations': ['student', 'member', 'alum', 'staff', 'employee'],
'eppn': ['[email protected]'],
'scopedAffiliations': [
'[email protected]', '[email protected]',
'[email protected]', '[email protected]',
'[email protected]'],
'isMemberOf': ['u_test_group', 'u_test_another_group']
}
if auth == 'SAML_MOCK':
MOCK_SAML_ATTRIBUTES = DEFAULT_SAML_ATTRIBUTES

else:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
DJANGO_LOGIN_MOCK_SAML = {
'NAME_ID': 'mock-nameid',
'SESSION_INDEX': 'mock-session',
'SAML_USERS': [{
'username': os.getenv('DJANGO_LOGIN_USERNAME', 'javerage'),
'password': os.getenv('DJANGO_LOGIN_PASSWORD', 'javerage'),
'email': os.getenv('DJANGO_LOGIN_EMAIL', '[email protected]'),
'MOCK_ATTRIBUTES': DEFAULT_SAML_ATTRIBUTES,
}]
}
_auth = auth_from_env('SAML')
if _auth:
INSTALLED_APPS.append('uw_saml')
LOGIN_URL = '/saml/login'
LOGOUT_URL = '/saml/logout'
SAML_USER_ATTRIBUTE = os.getenv('SAML_USER_ATTRIBUTE', 'uwnetid')
SAML_FORCE_AUTHN = parse_bool_from_str(os.getenv('SAML_FORCE_AUTHN', 'False'))

if _auth == 'SAML_MOCK' or _auth == 'SAML_DJANGO_LOGIN':
DEFAULT_SAML_ATTRIBUTES = {
'uwnetid': ['javerage'],
'affiliations': ['student', 'member', 'alum', 'staff', 'employee'],
'eppn': ['[email protected]'],
'scopedAffiliations': [
'[email protected]', '[email protected]',
'[email protected]', '[email protected]',
'[email protected]'],
'isMemberOf': ['u_test_group', 'u_test_another_group']
}
if _auth == 'SAML_MOCK':
MOCK_SAML_ATTRIBUTES = DEFAULT_SAML_ATTRIBUTES

else:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
DJANGO_LOGIN_MOCK_SAML = {
'NAME_ID': 'mock-nameid',
'SESSION_INDEX': 'mock-session',
'SAML_USERS': [{
'username': os.getenv('DJANGO_LOGIN_USERNAME', 'javerage'),
'password': os.getenv('DJANGO_LOGIN_PASSWORD', 'javerage'),
'email': os.getenv('DJANGO_LOGIN_EMAIL', '[email protected]'),
'MOCK_ATTRIBUTES': DEFAULT_SAML_ATTRIBUTES,
}]
}

elif auth == 'SAML':
CLUSTER_CNAME = os.getenv('CLUSTER_CNAME', 'localhost')
UW_SAML = {
'strict': True,
'debug': True,
'sp': {
'entityId': os.getenv('SAML_ENTITY_ID', 'https://' + CLUSTER_CNAME + '/saml'),
'assertionConsumerService': {
'url': 'https://' + CLUSTER_CNAME + '/saml/sso',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
},
'singleLogoutService': {
'url': 'https://' + CLUSTER_CNAME + LOGOUT_URL,
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'NameIDFormat': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
'x509cert': os.getenv('SP_CERT', ''),
'privateKey': os.getenv('SP_PRIVATE_KEY', ''),
elif _auth == 'SAML':
CLUSTER_CNAME = os.getenv('CLUSTER_CNAME', 'localhost')
UW_SAML = {
'strict': True,
'debug': True,
'sp': {
'entityId': os.getenv('SAML_ENTITY_ID', 'https://' + CLUSTER_CNAME + '/saml'),
'assertionConsumerService': {
'url': 'https://' + CLUSTER_CNAME + '/saml/sso',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
},
'idp': {
'entityId': 'urn:mace:incommon:washington.edu',
'singleSignOnService': {
'url': 'https://idp.u.washington.edu/idp/profile/SAML2/Redirect/SSO',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'singleLogoutService': {
'url': 'https://idp.u.washington.edu/idp/logout',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'x509cert': os.getenv('IDP_CERT', ''),
'singleLogoutService': {
'url': 'https://' + CLUSTER_CNAME + LOGOUT_URL,
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'security': {
'authnRequestsSigned': parse_bool_from_str(os.getenv('SP_AUTHN_REQUESTS_SIGNED', 'False')),
'wantMessagesSigned': parse_bool_from_str(os.getenv('SP_WANT_MESSAGES_SIGNED', 'True')),
'wantAssertionsSigned': parse_bool_from_str(os.getenv('SP_WANT_ASSERTIONS_SIGNED', 'False')),
'wantAssertionsEncrypted': parse_bool_from_str(os.getenv('SP_WANT_ASSERTIONS_ENCRYPTED', 'False')),
'requestedAuthnContext': [
'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken'
] if parse_bool_from_str(os.getenv('SP_USE_2FA', 'False')) else False,
'failOnAuthnContextMismatch': parse_bool_from_str(os.getenv('SP_USE_2FA', 'False')),
}
'NameIDFormat': 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
'x509cert': os.getenv('SP_CERT', ''),
'privateKey': os.getenv('SP_PRIVATE_KEY', ''),
},
'idp': {
'entityId': 'urn:mace:incommon:washington.edu',
'singleSignOnService': {
'url': 'https://idp.u.washington.edu/idp/profile/SAML2/Redirect/SSO',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'singleLogoutService': {
'url': 'https://idp.u.washington.edu/idp/logout',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
},
'x509cert': os.getenv('IDP_CERT', ''),
},
'security': {
'authnRequestsSigned': parse_bool_from_str(os.getenv('SP_AUTHN_REQUESTS_SIGNED', 'False')),
'wantMessagesSigned': parse_bool_from_str(os.getenv('SP_WANT_MESSAGES_SIGNED', 'True')),
'wantAssertionsSigned': parse_bool_from_str(os.getenv('SP_WANT_ASSERTIONS_SIGNED', 'False')),
'wantAssertionsEncrypted': parse_bool_from_str(os.getenv('SP_WANT_ASSERTIONS_ENCRYPTED', 'False')),
'requestedAuthnContext': [
'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken'
] if parse_bool_from_str(os.getenv('SP_USE_2FA', 'False')) else False,
'failOnAuthnContextMismatch': parse_bool_from_str(os.getenv('SP_USE_2FA', 'False')),
}
}

_auth = auth_from_env('BLTI')
if _auth:
INSTALLED_APPS.append('blti')

if auth.startswith('BLTI') and 'blti' not in INSTALLED_APPS:
INSTALLED_APPS.append('blti')
MIDDLEWARE.insert(0, 'blti.middleware.SessionHeaderMiddleware')
MIDDLEWARE.insert(0, 'blti.middleware.CSRFHeaderMiddleware')

MIDDLEWARE.insert(0, 'blti.middleware.SessionHeaderMiddleware')
MIDDLEWARE.insert(0, 'blti.middleware.CSRFHeaderMiddleware')
LTI_ENFORCE_SSL = parse_bool_from_str(os.getenv('LTI_ENFORCE_SSL', 'False'))

if _auth == 'BLTI_DEV' and os.getenv('ENV', 'localdev') == 'localdev':
LTI_DEVELOP_APP = os.getenv('LTI_DEVELOP_APP', '')
LTI_CONSUMERS = {'0000-0000-0000': '01234567ABCDEF'}
BLTI_AES_KEY = bytes('AE91AE1DF0E6FB44', encoding='utf8')
BLTI_AES_IV = bytes('01C8837249AE8667', encoding='utf8')
else:
# BLTI consumer key:secret pairs in env as a serialized dict
LTI_CONSUMERS = json.loads(os.getenv('LTI_CONSUMERS', '{}'))
LTI_ENFORCE_SSL = parse_bool_from_str(os.getenv('LTI_ENFORCE_SSL', 'False'))

# BLTI session object encryption values
BLTI_AES_KEY = bytes(os.getenv('BLTI_AES_KEY', ''), encoding='utf8')
Expand Down
14 changes: 14 additions & 0 deletions project/base_settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@
}
}

MEMCACHED_SERVER_COUNT = int(os.getenv('MEMCACHED_SERVER_COUNT', 0))
if (os.getenv('SESSION_BACKEND', '') == 'MEMCACHED'
and MEMCACHED_SERVER_COUNT > 0):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': [MEMCACHED_SERVER_SPEC.format(n)
for n in range(
MEMCACHED_SERVER_COUNT)]

}
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
Expand Down
4 changes: 2 additions & 2 deletions project/base_settings/setting_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# list of all utility attributes
list_of_attributes = ['list_of_attributes', 'parse_bool_from_str']
list_of_attributes = ['list_of_attributes', 'parse_bool_from_str', 'auth_from_env', '_auth']

def parse_bool_from_str(string):
return string.lower() in ['true', '1', 't', 'y', 'yes']
return string.lower() in ['true', '1', 't', 'y', 'yes']
37 changes: 32 additions & 5 deletions tests/test_settings/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,33 @@ def test_attributes(self):
self.assertDictEqual({"0000-0000-0000": "01234567ABCDEF"}, base_settings.LTI_CONSUMERS)


class BLTIDevTest(TestCase):
def setUp(self):
self.mock_env = {
'AUTH': 'BLTI_DEV',
'LTI_DEVELOP_APP': 'test_app',
}

def test_attributes(self):
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertIn('blti', base_settings.INSTALLED_APPS)
self.assertIn('blti.middleware.SessionHeaderMiddleware', base_settings.MIDDLEWARE)
self.assertIn('blti.middleware.CSRFHeaderMiddleware', base_settings.MIDDLEWARE)
self.assertEqual('test_app', base_settings.LTI_DEVELOP_APP)
self.assertDictEqual({"0000-0000-0000": "01234567ABCDEF"}, base_settings.LTI_CONSUMERS)

def test_attributes_wrong_env(self):
self.mock_env['ENV'] = 'test'
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertIn('blti', base_settings.INSTALLED_APPS)
self.assertIn('blti.middleware.SessionHeaderMiddleware', base_settings.MIDDLEWARE)
self.assertIn('blti.middleware.CSRFHeaderMiddleware', base_settings.MIDDLEWARE)

# DEV settings should not have been added
self.assertFalse(hasattr(base_settings, 'LTI_DEVELOP_APP'))
self.assertDictEqual({}, base_settings.LTI_CONSUMERS)


class MultipleAuthTest(TestCase):
def test_valid_auth_list(self):
mock_env = {
Expand All @@ -114,12 +141,12 @@ def test_valid_auth_list(self):

def test_invalid_auth_list(self):
mock_env = {
'AUTH': 'SAML SAML_MOCK',
'AUTH': 'SAML_MOCK SAML',
}

# Ensure that only the first occurence of a SAML type was configured
with SettingLoader('project.base_settings', **mock_env) as base_settings:
self.assertIn('uw_saml', base_settings.INSTALLED_APPS)

# uw_saml app must not appear in INSTALLED_APPS more then once
self.assertEqual(len(set(base_settings.INSTALLED_APPS)),
len(base_settings.INSTALLED_APPS))
self.assertIsNotNone(base_settings.MOCK_SAML_ATTRIBUTES)
self.assertDictEqual(base_settings.DEFAULT_SAML_ATTRIBUTES, base_settings.MOCK_SAML_ATTRIBUTES)
self.assertFalse(hasattr(base_settings, 'UW_SAML'))

0 comments on commit 862adeb

Please sign in to comment.