diff options
| author | Ionuț Arțăriși <iartarisi@suse.cz> | 2013-02-11 17:15:23 +0100 |
|---|---|---|
| committer | Ionuț Arțăriși <iartarisi@suse.cz> | 2013-02-18 17:02:44 +0100 |
| commit | 159ffe48e986e524f5930ad41d376bdce2b6a07e (patch) | |
| tree | eddb5a177f2b29265b687087a2411608a832737e | |
| parent | 63f6e87c5e267a015f4d6054c0941c0efc8526f1 (diff) | |
make LDAP query scope configurable
Get the DN from the LDAP server itself rather than hardcoding its format.
Fixes bug 1122181
Change-Id: I6f70c480b5c6f1b064e74d3cbd2cd8ca5ee82b0a
| -rw-r--r-- | etc/keystone.conf.sample | 4 | ||||
| -rw-r--r-- | keystone/common/ldap/core.py | 42 | ||||
| -rw-r--r-- | keystone/config.py | 1 | ||||
| -rw-r--r-- | tests/test_backend_ldap.py | 12 |
4 files changed, 49 insertions, 10 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 8eddfe1a..b954946b 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -130,6 +130,10 @@ # allow_subtree_delete = False # dumb_member = cn=dumb,dc=example,dc=com +# The LDAP scope for queries, this can be either 'one' +# (onelevel/singleLevel) or 'sub' (subtree/wholeSubtree) +# query_scope = one + # user_tree_dn = ou=Users,dc=example,dc=com # user_filter = # user_objectclass = inetOrgPerson diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py index eca2084d..2c1d0817 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -26,6 +26,8 @@ LOG = logging.getLogger(__name__) 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} def py2ldap(val): @@ -59,6 +61,14 @@ def safe_iter(attrs): yield attrs +def ldap_scope(scope): + try: + return LDAP_SCOPES[scope] + except KeyError: + raise ValueError(_('Invalid LDAP scope: %s. Choose one of: ' % scope) + + ', '.join(LDAP_SCOPES.keys())) + + class BaseLdap(object): DEFAULT_SUFFIX = "dc=example,dc=com" DEFAULT_OU = None @@ -77,6 +87,7 @@ class BaseLdap(object): self.LDAP_URL = conf.ldap.url self.LDAP_USER = conf.ldap.user self.LDAP_PASSWORD = conf.ldap.password + self.LDAP_SCOPE = ldap_scope(conf.ldap.query_scope) if self.options_name is not None: self.suffix = conf.ldap.suffix @@ -133,9 +144,18 @@ class BaseLdap(object): return conn def _id_to_dn(self, id): - return '%s=%s,%s' % (self.id_attr, - ldap.dn.escape_dn_chars(str(id)), - self.tree_dn) + conn = self.get_connection() + try: + dn, attrs = conn.search_s( + self.tree_dn, self.LDAP_SCOPE, + '(&(%(id_attr)s=%(id)s)(objectclass=%(objclass)s))' % + {'id_attr': self.id_attr, + 'id': ldap.filter.escape_filter_chars(str(id)), + 'objclass': self.object_class})[0] + except ValueError, IndexError: + raise ldap.NO_SUCH_OBJECT + else: + return dn @staticmethod def _dn_to_id(dn): @@ -203,16 +223,18 @@ class BaseLdap(object): def _ldap_get(self, id, filter=None): conn = self.get_connection() - query = '(&%s(objectClass=%s))' % (filter or self.filter or '', - self.object_class) + query = ('(&(%(id_attr)s=%(id)s)' + '%(filter)s' + '(objectClass=%(object_class)s))' + % {'id_attr': self.id_attr, + 'id': ldap.filter.escape_filter_chars(str(id)), + 'filter': (filter or self.filter or ''), + 'object_class': self.object_class}) try: - res = conn.search_s(self._id_to_dn(id), - ldap.SCOPE_BASE, - query, + res = conn.search_s(self.tree_dn, self.LDAP_SCOPE, query, self.attribute_mapping.values()) except ldap.NO_SUCH_OBJECT: return None - try: return res[0] except IndexError: @@ -224,7 +246,7 @@ class BaseLdap(object): self.object_class) try: return conn.search_s(self.tree_dn, - ldap.SCOPE_ONELEVEL, + self.LDAP_SCOPE, query, self.attribute_mapping.values()) except ldap.NO_SUCH_OBJECT: diff --git a/keystone/config.py b/keystone/config.py index b604db43..127d09d6 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -245,6 +245,7 @@ register_str('suffix', group='ldap', default='cn=example,cn=com') register_bool('use_dumb_member', group='ldap', default=False) register_str('dumb_member', group='ldap', default='cn=dumb,dc=nonexistent') register_bool('allow_subtree_delete', group='ldap', default=False) +register_str('query_scope', group='ldap', default='one') register_str('user_tree_dn', group='ldap', default=None) register_str('user_filter', group='ldap', default=None) diff --git a/tests/test_backend_ldap.py b/tests/test_backend_ldap.py index b5ad68b3..8a116cfa 100644 --- a/tests/test_backend_ldap.py +++ b/tests/test_backend_ldap.py @@ -14,9 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. +import ldap import uuid import nose.exc +from keystone.common import ldap as ldap_common from keystone.common.ldap import fakeldap from keystone import config from keystone import exception @@ -42,6 +44,9 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): test.testsdir('test_overrides.conf'), test.testsdir('backend_ldap.conf')]) clear_database() + self.stubs.Set(ldap_common.BaseLdap, "_id_to_dn", + lambda self, id: '%s=%s,%s' % (self.id_attr, + str(id), self.tree_dn)) self.identity_api = identity_ldap.Identity() self.load_fixtures(default_fixtures) @@ -347,6 +352,13 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests): user_api.get_connection(user=None, password=None) + def test_wrong_ldap_scope(self): + CONF.ldap.query_scope = uuid.uuid4().hex + self.assertRaisesRegexp( + ValueError, + 'Invalid LDAP scope: %s. *' % CONF.ldap.query_scope, + identity_ldap.Identity) + # TODO (henry-nash) These need to be removed when the full LDAP implementation # is submitted - see Bugs 1092187, 1101287, 1101276, 1101289 def test_group_crud(self): |
