summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIonuț Arțăriși <iartarisi@suse.cz>2013-02-11 17:15:23 +0100
committerIonuț Arțăriși <iartarisi@suse.cz>2013-02-18 17:02:44 +0100
commit159ffe48e986e524f5930ad41d376bdce2b6a07e (patch)
treeeddb5a177f2b29265b687087a2411608a832737e
parent63f6e87c5e267a015f4d6054c0941c0efc8526f1 (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.sample4
-rw-r--r--keystone/common/ldap/core.py42
-rw-r--r--keystone/config.py1
-rw-r--r--tests/test_backend_ldap.py12
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):