diff options
| -rw-r--r-- | etc/keystone.conf.sample | 5 | ||||
| -rw-r--r-- | keystone/common/config.py | 1 | ||||
| -rw-r--r-- | keystone/common/ldap/core.py | 21 | ||||
| -rw-r--r-- | tests/_ldap_livetest.py | 71 | ||||
| -rw-r--r-- | tests/test_backend_ldap.py | 7 |
5 files changed, 103 insertions, 2 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 426e3b24..b39eb926 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -140,6 +140,11 @@ # Maximum results per page; a value of zero ('0') disables paging (default) # page_size = 0 +# The LDAP dereferencing option for queries. This can be either 'never', +# 'searching', 'always', 'finding' or 'default'. The 'default' option falls +# back to using default dereferencing configured by your ldap.conf. +# alias_dereferencing = default + # The LDAP scope for queries, this can be either 'one' # (onelevel/singleLevel) or 'sub' (subtree/wholeSubtree) # query_scope = one diff --git a/keystone/common/config.py b/keystone/common/config.py index e60385cc..2fd20b99 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -268,6 +268,7 @@ def configure(): register_bool('allow_subtree_delete', group='ldap', default=False) register_str('query_scope', group='ldap', default='one') register_int('page_size', group='ldap', default=0) + register_str('alias_dereferencing', group='ldap', default='default') register_str('user_tree_dn', group='ldap', default=None) register_str('user_filter', group='ldap', default=None) diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py index 865c90e7..b06f2277 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -29,6 +29,11 @@ LDAP_VALUES = {'TRUE': True, 'FALSE': False} CONTROL_TREEDELETE = '1.2.840.113556.1.4.805' LDAP_SCOPES = {'one': ldap.SCOPE_ONELEVEL, 'sub': ldap.SCOPE_SUBTREE} +LDAP_DEREF = {'always': ldap.DEREF_ALWAYS, + 'default': None, + 'finding': ldap.DEREF_FINDING, + 'never': ldap.DEREF_NEVER, + 'searching': ldap.DEREF_SEARCHING} def py2ldap(val): @@ -62,6 +67,14 @@ def safe_iter(attrs): yield attrs +def parse_deref(opt): + try: + return LDAP_DEREF[opt] + except KeyError: + raise ValueError((_('Invalid LDAP deref option: %s. Choose one of: ') % + opt) + ', '.join(LDAP_DEREF.keys())) + + def ldap_scope(scope): try: return LDAP_SCOPES[scope] @@ -91,6 +104,7 @@ class BaseLdap(object): self.LDAP_USER = conf.ldap.user self.LDAP_PASSWORD = conf.ldap.password self.LDAP_SCOPE = ldap_scope(conf.ldap.query_scope) + self.alias_dereferencing = parse_deref(conf.ldap.alias_dereferencing) self.page_size = conf.ldap.page_size if self.options_name is not None: @@ -142,7 +156,8 @@ class BaseLdap(object): conn = fakeldap.FakeLdap(self.LDAP_URL) else: conn = LdapWrapper(self.LDAP_URL, - self.page_size) + self.page_size, + alias_dereferencing=self.alias_dereferencing) if user is None: user = self.LDAP_USER @@ -348,9 +363,11 @@ class BaseLdap(object): class LdapWrapper(object): - def __init__(self, url, page_size): + def __init__(self, url, page_size, alias_dereferencing=None): LOG.debug(_("LDAP init: url=%s"), url) self.conn = ldap.initialize(url) + if alias_dereferencing is not None: + self.conn.set_option(ldap.OPT_DEREF, alias_dereferencing) self.page_size = page_size def simple_bind_s(self, user, password): diff --git a/tests/_ldap_livetest.py b/tests/_ldap_livetest.py index d6a7a63d..5d9e96d0 100644 --- a/tests/_ldap_livetest.py +++ b/tests/_ldap_livetest.py @@ -19,7 +19,9 @@ import ldap.modlist import nose.exc import subprocess +from keystone.common import ldap as ldap_common from keystone import config +from keystone import exception from keystone.identity.backends import ldap as identity_ldap from keystone import test @@ -92,3 +94,72 @@ class LiveLDAPIdentity(test_backend_ldap.LDAPIdentity): def test_user_enable_attribute_mask(self): raise nose.exc.SkipTest('Test is for Active Directory Only') + + def test_ldap_dereferencing(self): + alt_users_ldif = {'objectclass': ['top', 'organizationalUnit'], + 'ou': 'alt_users'} + alt_fake_user_ldif = {'objectclass': ['person', 'inetOrgPerson'], + 'cn': 'alt_fake1', + 'sn': 'alt_fake1'} + aliased_users_ldif = {'objectclass': ['alias', 'extensibleObject'], + 'aliasedobjectname': "ou=alt_users,%s" % + CONF.ldap.suffix} + create_object("ou=alt_users,%s" % CONF.ldap.suffix, alt_users_ldif) + create_object("%s=alt_fake1,ou=alt_users,%s" % + (CONF.ldap.user_id_attribute, CONF.ldap.suffix), + alt_fake_user_ldif) + create_object("ou=alt_users,%s" % CONF.ldap.user_tree_dn, + aliased_users_ldif) + + CONF.ldap.query_scope = 'sub' + CONF.ldap.alias_dereferencing = 'never' + self.identity_api = identity_ldap.Identity() + self.assertRaises(exception.UserNotFound, + self.identity_api.get_user, + 'alt_fake1') + + CONF.ldap.alias_dereferencing = 'searching' + self.identity_api = identity_ldap.Identity() + user_ref = self.identity_api.get_user('alt_fake1') + self.assertEqual(user_ref['id'], 'alt_fake1') + + CONF.ldap.alias_dereferencing = 'always' + self.identity_api = identity_ldap.Identity() + user_ref = self.identity_api.get_user('alt_fake1') + self.assertEqual(user_ref['id'], 'alt_fake1') + + def test_base_ldap_connection_deref_option(self): + deref = ldap_common.parse_deref('default') + ldap_wrapper = ldap_common.LdapWrapper(CONF.ldap.url, + CONF.ldap.page_size, + alias_dereferencing=deref) + self.assertEqual(ldap.get_option(ldap.OPT_DEREF), + ldap_wrapper.conn.get_option(ldap.OPT_DEREF)) + + deref = ldap_common.parse_deref('always') + ldap_wrapper = ldap_common.LdapWrapper(CONF.ldap.url, + CONF.ldap.page_size, + alias_dereferencing=deref) + self.assertEqual(ldap.DEREF_ALWAYS, + ldap_wrapper.conn.get_option(ldap.OPT_DEREF)) + + deref = ldap_common.parse_deref('finding') + ldap_wrapper = ldap_common.LdapWrapper(CONF.ldap.url, + CONF.ldap.page_size, + alias_dereferencing=deref) + self.assertEqual(ldap.DEREF_FINDING, + ldap_wrapper.conn.get_option(ldap.OPT_DEREF)) + + deref = ldap_common.parse_deref('never') + ldap_wrapper = ldap_common.LdapWrapper(CONF.ldap.url, + CONF.ldap.page_size, + alias_dereferencing=deref) + self.assertEqual(ldap.DEREF_NEVER, + ldap_wrapper.conn.get_option(ldap.OPT_DEREF)) + + deref = ldap_common.parse_deref('searching') + ldap_wrapper = ldap_common.LdapWrapper(CONF.ldap.url, + CONF.ldap.page_size, + alias_dereferencing=deref) + self.assertEqual(ldap.DEREF_SEARCHING, + ldap_wrapper.conn.get_option(ldap.OPT_DEREF)) diff --git a/tests/test_backend_ldap.py b/tests/test_backend_ldap.py index b0749d9e..6db626d8 100644 --- a/tests/test_backend_ldap.py +++ b/tests/test_backend_ldap.py @@ -357,6 +357,13 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): 'Invalid LDAP scope: %s. *' % CONF.ldap.query_scope, identity.backends.ldap.Identity) + def test_wrong_alias_dereferencing(self): + CONF.ldap.alias_dereferencing = uuid.uuid4().hex + self.assertRaisesRegexp( + ValueError, + 'Invalid LDAP deref option: %s\.' % CONF.ldap.alias_dereferencing, + identity.backends.ldap.Identity) + # TODO (henry-nash) These need to be removed when the full LDAP implementation # is submitted - see Bugs 1092187, 1101287, 1101276, 1101289 |
