AC-156 Added code and tests to support LDAP authentication (no organization or team mapping yet).

This commit is contained in:
Chris Church
2013-09-07 16:14:43 -04:00
parent e8b88ed57e
commit 916dd713e3
7 changed files with 235 additions and 1 deletions

36
awx/main/backend.py Normal file
View File

@@ -0,0 +1,36 @@
# Copyright (c) 2013 AnsibleWorks, Inc.
# All Rights Reserved.
# django-auth-ldap
from django_auth_ldap.backend import LDAPBackend as BaseLDAPBackend
class LDAPBackend(BaseLDAPBackend):
'''
Custom LDAP backend for AWX.
'''
settings_prefix = 'AUTH_LDAP_'
def authenticate(self, username, password):
if not self.settings.SERVER_URI:
return None
return super(LDAPBackend, self).authenticate(username, password)
def get_user(self, user_id):
if not self.settings.SERVER_URI:
return None
return super(LDAPBackend, self).get_user(user_id)
# Disable any LDAP based authorization / permissions checking.
def has_perm(self, user, perm, obj=None):
return False
def has_module_perms(self, user, app_label):
return False
def get_all_permissions(self, user, obj=None):
return set()
def get_group_permissions(self, user, obj=None):
return set()

View File

@@ -2,7 +2,7 @@
# All Rights Reserved. # All Rights Reserved.
from awx.main.tests.organizations import OrganizationsTest from awx.main.tests.organizations import OrganizationsTest
from awx.main.tests.users import UsersTest from awx.main.tests.users import *
from awx.main.tests.inventory import InventoryTest from awx.main.tests.inventory import InventoryTest
from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest
from awx.main.tests.commands import * from awx.main.tests.commands import *

View File

@@ -7,15 +7,21 @@ import json
import urllib import urllib
# Django # Django
from django.conf import settings, UserSettingsHolder
from django.contrib.auth.models import User from django.contrib.auth.models import User
import django.test import django.test
from django.test.client import Client from django.test.client import Client
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
# Django-Auth-LDAP
from django_auth_ldap.backend import LDAPSettings
# AWX # AWX
from awx.main.models import * from awx.main.models import *
from awx.main.tests.base import BaseTest from awx.main.tests.base import BaseTest
__all__ = ['UsersTest', 'LdapTest']
class UsersTest(BaseTest): class UsersTest(BaseTest):
def collection(self): def collection(self):
@@ -621,3 +627,68 @@ class UsersTest(BaseTest):
self.check_get_list(url, self.super_django_user, qs) self.check_get_list(url, self.super_django_user, qs)
url = u'%s?user\u2605name=normal' % base_url url = u'%s?user\u2605name=normal' % base_url
self.check_get_list(url, self.super_django_user, base_qs, expect=400) self.check_get_list(url, self.super_django_user, base_qs, expect=400)
class LdapTest(BaseTest):
def use_test_setting(self, name, default=None):
setattr(settings, 'AUTH_LDAP_%s' % name,
getattr(settings, 'TEST_AUTH_LDAP_%s' % name, default))
def setUp(self):
super(LdapTest, self).setUp()
# Skip tests if basic LDAP test settings aren't defined.
if not getattr(settings, 'TEST_AUTH_LDAP_SERVER_URI', None):
self.skipTest('no test LDAP auth server defined')
self.ldap_username = getattr(settings, 'TEST_AUTH_LDAP_USERNAME', None)
if not self.ldap_username:
self.skipTest('no test LDAP username defined')
self.ldap_password = getattr(settings, 'TEST_AUTH_LDAP_PASSWORD', None)
if not self.ldap_password:
self.skipTest('no test LDAP password defined')
# Wrap settings so we can redfine them for each test.
self._wrapped = settings._wrapped
settings._wrapped = UserSettingsHolder(settings._wrapped)
# Reset all AUTH_LDAP_* settings to defaults.
for name, value in LDAPSettings.defaults.items():
setattr(settings, 'AUTH_LDAP_%s' % name, value)
# Set test LDAP settings that are always needed.
for name in ('SERVER_URI', 'BIND_DN', 'BIND_PASSWORD', 'USE_TLS'):
self.use_test_setting(name)
def tearDown(self):
super(LdapTest, self).tearDown()
settings._wrapped = self._wrapped
def check_login(self, username=None, password=None, should_fail=False):
username = username or self.ldap_username
password = password or self.ldap_password
result = self.client.login(username=username, password=password)
self.assertNotEqual(result, should_fail)
if not should_fail:
return User.objects.get(username=username)
def test_ldap_auth(self):
self.use_test_setting('USER_SEARCH')
self.assertEqual(User.objects.filter(username=self.ldap_username).count(), 0)
# Test logging in, user should be created with no flags or fields set.
user = self.check_login()
self.assertTrue(user.is_active)
self.assertFalse(user.has_usable_password())
self.assertFalse(user.is_superuser)
self.assertFalse(user.first_name)
self.assertFalse(user.last_name)
self.assertFalse(user.email)
# Test logging in with bad username or password.
self.check_login(username='not a valid user', should_fail=True)
self.check_login(password='not a valid pass', should_fail=True)
# Test using a flat DN instead of user search.
self.use_test_setting('USER_DN_TEMPLATE', None)
if settings.AUTH_LDAP_USER_DN_TEMPLATE:
user = self.check_login()
del settings.AUTH_LDAP_USER_DN_TEMPLATE
# Test user attributes assigned from LDAP.
self.use_test_setting('USER_ATTR_MAP', {})
if settings.AUTH_LDAP_USER_ATTR_MAP:
user = self.check_login()
for attr in settings.AUTH_LDAP_USER_ATTR_MAP.keys():
self.assertTrue(getattr(user, attr))

View File

@@ -165,6 +165,14 @@ REST_FRAMEWORK = {
), ),
} }
AUTHENTICATION_BACKENDS = (
'awx.main.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
# LDAP server (default to None to skip using LDAP authentication).
AUTH_LDAP_SERVER_URI = None
# Seconds before auth tokens expire. # Seconds before auth tokens expire.
AUTH_TOKEN_EXPIRATION = 1800 AUTH_TOKEN_EXPIRATION = 1800
@@ -345,5 +353,8 @@ LOGGING = {
'handlers': ['null'], 'handlers': ['null'],
'propagate': False, 'propagate': False,
}, },
'django_auth_ldap': {
'handlers': ['null'],
},
} }
} }

View File

@@ -60,6 +60,33 @@ SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y'
# reverse proxy. # reverse proxy.
REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST'] REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST']
# LDAP connection and authentication settings. Refer to django-auth-ldap docs:
# http://pythonhosted.org/django-auth-ldap/authentication.html
AUTH_LDAP_SERVER_URI = ''
AUTH_LDAP_BIND_DN = ''
AUTH_LDAP_BIND_PASSWORD = ''
AUTH_LDAP_START_TLS = False
import ldap
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
# LDAP search query to find users.
AUTH_LDAP_USER_SEARCH = LDAPSearch(
'OU=Users,DC=example,DC=com',
ldap.SCOPE_SUBTREE,
'(sAMAccountName=%(user)s)',
)
# Alternative to user search.
#AUTH_LDAP_USER_DN_TEMPLATE = 'sAMAccountName=%(user)s,OU=Users,DC=example,DC=com'
# Mapping of LDAP attributes to user attributes.
AUTH_LDAP_USER_ATTR_MAP = {
'first_name': 'givenName',
'last_name': 'sn',
'email': 'mail',
}
# Email address that error messages come from. # Email address that error messages come from.
SERVER_EMAIL = 'root@localhost' SERVER_EMAIL = 'root@localhost'
@@ -111,6 +138,10 @@ LOGGING['handlers']['syslog'] = {
#LOGGING['loggers']['awx.main.signals']['propagate'] = True #LOGGING['loggers']['awx.main.signals']['propagate'] = True
#LOGGING['loggers']['awx.main.permissions']['propagate'] = True #LOGGING['loggers']['awx.main.permissions']['propagate'] = True
# Enable the following lines to turn on LDAP auth logging.
#LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console']
#LOGGING['loggers']['django_auth_ldap']['level'] = 'DEBUG'
# Define additional environment variables to be passed to subprocess started by # Define additional environment variables to be passed to subprocess started by
# the celery task. # the celery task.
#AWX_TASK_ENV['FOO'] = 'BAR' #AWX_TASK_ENV['FOO'] = 'BAR'
@@ -141,3 +172,32 @@ TEST_SVN_USERNAME = ''
TEST_SVN_PASSWORD = '' TEST_SVN_PASSWORD = ''
TEST_SVN_PUBLIC_HTTPS = 'https://projects.ninemoreminutes.com/svn/django-site-utils/trunk/' TEST_SVN_PUBLIC_HTTPS = 'https://projects.ninemoreminutes.com/svn/django-site-utils/trunk/'
TEST_SVN_PRIVATE_HTTPS = '' TEST_SVN_PRIVATE_HTTPS = ''
# LDAP connection and authentication settings for unit tests only. LDAP tests
# will be skipped if not configured. Refer to django-auth-ldap docs:
# http://pythonhosted.org/django-auth-ldap/authentication.html
TEST_AUTH_LDAP_SERVER_URI = ''
TEST_AUTH_LDAP_BIND_DN = ''
TEST_AUTH_LDAP_BIND_PASSWORD = ''
TEST_AUTH_LDAP_START_TLS = False
# LDAP username/password for testing authentication.
TEST_AUTH_LDAP_USERNAME = ''
TEST_AUTH_LDAP_PASSWORD = ''
# LDAP search query to find users.
TEST_AUTH_LDAP_USER_SEARCH = LDAPSearch(
'OU=Users,DC=example,DC=com',
ldap.SCOPE_SUBTREE,
'(sAMAccountName=%(user)s)',
)
# Alternative to user search.
TEST_AUTH_LDAP_USER_DN_TEMPLATE = 'sAMAccountName=%(user)s,OU=Users,DC=example,DC=com'
# Mapping of LDAP attributes to user attributes.
TEST_AUTH_LDAP_USER_ATTR_MAP = {
'first_name': 'givenName',
'last_name': 'sn',
'email': 'mail',
}

View File

@@ -54,3 +54,31 @@ EMAIL_PORT = 25
EMAIL_HOST_USER = '' EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = '' EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
# LDAP connection and authentication settings. Refer to django-auth-ldap docs:
# http://pythonhosted.org/django-auth-ldap/authentication.html
AUTH_LDAP_SERVER_URI = ''
AUTH_LDAP_BIND_DN = ''
AUTH_LDAP_BIND_PASSWORD = ''
AUTH_LDAP_START_TLS = False
#import ldap
#from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
# LDAP search query to find users.
#AUTH_LDAP_USER_SEARCH = LDAPSearch(
# 'OU=Users,DC=example,DC=com',
# ldap.SCOPE_SUBTREE,
# '(sAMAccountName=%(user)s)',
#)
# Alternative to user search.
#AUTH_LDAP_USER_DN_TEMPLATE = 'sAMAccountName=%(user)s,OU=Users,DC=example,DC=com'
# Mapping of LDAP attributes to user attributes.
#AUTH_LDAP_USER_ATTR_MAP = {
# 'first_name': 'givenName',
# 'last_name': 'sn',
# 'email': 'mail',
#}

View File

@@ -54,3 +54,31 @@ EMAIL_PORT = 25
EMAIL_HOST_USER = '' EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = '' EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
# LDAP connection and authentication settings. Refer to django-auth-ldap docs:
# http://pythonhosted.org/django-auth-ldap/authentication.html
AUTH_LDAP_SERVER_URI = ''
AUTH_LDAP_BIND_DN = ''
AUTH_LDAP_BIND_PASSWORD = ''
AUTH_LDAP_START_TLS = False
#import ldap
#from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
# LDAP search query to find users.
#AUTH_LDAP_USER_SEARCH = LDAPSearch(
# 'OU=Users,DC=example,DC=com',
# ldap.SCOPE_SUBTREE,
# '(sAMAccountName=%(user)s)',
#)
# Alternative to user search.
#AUTH_LDAP_USER_DN_TEMPLATE = 'sAMAccountName=%(user)s,OU=Users,DC=example,DC=com'
# Mapping of LDAP attributes to user attributes.
#AUTH_LDAP_USER_ATTR_MAP = {
# 'first_name': 'givenName',
# 'last_name': 'sn',
# 'email': 'mail',
#}