Skip to content

Commit

Permalink
Merge pull request #107 from uw-it-aca/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jlaney authored Aug 27, 2020
2 parents de09de5 + 9784754 commit 437cc85
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 84 deletions.
158 changes: 87 additions & 71 deletions project/base_settings/auth_settings.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,97 @@
import os
from .common import INSTALLED_APPS, AUTHENTICATION_BACKENDS
import json
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')

if os.getenv('AUTH', '').startswith('SAML'):
INSTALLED_APPS += ['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'))
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 os.getenv('AUTH', '') == 'SAML_MOCK' or os.getenv('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 os.getenv('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,
}]
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

elif os.getenv('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', ''),
},
'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'
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', ''),
},
'singleLogoutService': {
'url': 'https://idp.u.washington.edu/idp/logout',
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
'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', ''),
},
'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')),
'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')),
}
}
}

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')

# 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')
BLTI_AES_IV = bytes(os.getenv('BLTI_AES_IV', ''), encoding='utf8')
8 changes: 3 additions & 5 deletions project/base_settings/prometheus_settings.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import os
from .common import INSTALLED_APPS, MIDDLEWARE, DATABASES

INSTALLED_APPS.append('django_prometheus')

INSTALLED_APPS += ['django_prometheus']

MIDDLEWARE = ['django_prometheus.middleware.PrometheusBeforeMiddleware'] + \
MIDDLEWARE + \
['django_prometheus.middleware.PrometheusAfterMiddleware']
MIDDLEWARE.insert(0, 'django_prometheus.middleware.PrometheusBeforeMiddleware')
MIDDLEWARE.append('django_prometheus.middleware.PrometheusAfterMiddleware')

if os.getenv('DB', 'sqlite3') == 'sqlite3':
DATABASES['default']['ENGINE'] = 'django_prometheus.db.backends.sqlite3'
Expand Down
61 changes: 53 additions & 8 deletions tests/test_settings/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from unittest import TestCase
from ..utils import SettingLoader

class BaseAuthTest:

class SAMLBaseAuthTest:
def test_common_attributes(self):
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertIn('uw_saml', base_settings.INSTALLED_APPS)
Expand All @@ -20,7 +21,8 @@ def test_common_attributes(self):
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertFalse(base_settings.SAML_FORCE_AUTHN)

class TestNoAuthBackend(TestCase):

class NoAuthBackendTest(TestCase):
def test_common_attributes(self):
with SettingLoader('project.base_settings') as base_settings:
self.assertNotIn('uw_saml', base_settings.INSTALLED_APPS)
Expand All @@ -29,7 +31,8 @@ def test_common_attributes(self):
self.assertFalse(hasattr(base_settings, 'SAML_USER_ATTRIBUTE'))
self.assertFalse(hasattr(base_settings, 'SAML_FORCE_AUTHN'))

class TestSAML(TestCase, BaseAuthTest):

class SAMLTest(TestCase, SAMLBaseAuthTest):
def setUp(self):
self.mock_env = {
'AUTH': 'SAML',
Expand All @@ -39,11 +42,12 @@ def setUp(self):
def test_attributes(self):
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertEqual(self.mock_env['CLUSTER_CNAME'], base_settings.CLUSTER_CNAME)

# Just tests that UW_SAML attributes exists
self.assertIsNotNone(base_settings.UW_SAML)

class TestSAMLMOCK(TestCase, BaseAuthTest):

class SAMLMockTest(TestCase, SAMLBaseAuthTest):
def setUp(self):
self.mock_env = {
'AUTH': 'SAML_MOCK',
Expand All @@ -53,10 +57,11 @@ def test_attributes(self):
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertIsNotNone(base_settings.DEFAULT_SAML_ATTRIBUTES)
self.assertIsNotNone(base_settings.MOCK_SAML_ATTRIBUTES)

self.assertDictEqual(base_settings.DEFAULT_SAML_ATTRIBUTES, base_settings.MOCK_SAML_ATTRIBUTES)

class TestSAMLDJANGOLOGIN(TestCase, BaseAuthTest):

class SAMLDjangoLoginTest(TestCase, SAMLBaseAuthTest):
def setUp(self):
self.mock_env = {
'AUTH': 'SAML_DJANGO_LOGIN',
Expand All @@ -73,8 +78,48 @@ def test_attributes(self):
def test_django_user_conf(self):
with SettingLoader('project.base_settings', **self.mock_env) as base_settings:
self.assertEqual(len(base_settings.DJANGO_LOGIN_MOCK_SAML['SAML_USERS']), 1)

self.assertEqual(base_settings.DJANGO_LOGIN_MOCK_SAML['SAML_USERS'][0]['username'], self.mock_env['DJANGO_LOGIN_USERNAME'])
self.assertEqual(base_settings.DJANGO_LOGIN_MOCK_SAML['SAML_USERS'][0]['password'], self.mock_env['DJANGO_LOGIN_PASSWORD'])
self.assertEqual(base_settings.DJANGO_LOGIN_MOCK_SAML['SAML_USERS'][0]['email'], self.mock_env['DJANGO_LOGIN_EMAIL'])
self.assertDictEqual(base_settings.DJANGO_LOGIN_MOCK_SAML['SAML_USERS'][0]['MOCK_ATTRIBUTES'], base_settings.DEFAULT_SAML_ATTRIBUTES)


class BLTITest(TestCase):
def setUp(self):
self.mock_env = {
'AUTH': 'BLTI',
'LTI_CONSUMERS': '{"0000-0000-0000": "01234567ABCDEF"}',
'BLTI_AES_KEY': 'AE91AE1DF0E6FB44',
'BLTI_AES_IV': '01C8837249AE8667',
}

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.assertDictEqual({"0000-0000-0000": "01234567ABCDEF"}, base_settings.LTI_CONSUMERS)


class MultipleAuthTest(TestCase):
def test_valid_auth_list(self):
mock_env = {
'AUTH': 'SAML BLTI',
}

with SettingLoader('project.base_settings', **mock_env) as base_settings:
self.assertIn('uw_saml', base_settings.INSTALLED_APPS)
self.assertIn('blti', base_settings.INSTALLED_APPS)

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

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))

0 comments on commit 437cc85

Please sign in to comment.