diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index b50ec7603a..808cff757a 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -137,6 +137,7 @@ # use_dumb_member = False # allow_subtree_delete = False # dumb_member = cn=dumb,dc=example,dc=com +# page_size = 0 # The LDAP scope for queries, this can be either 'one' # (onelevel/singleLevel) or 'sub' (subtree/wholeSubtree) diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py index f0a5cac676..7760ba2578 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -88,6 +88,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.page_size = conf.ldap.page_size if self.options_name is not None: self.suffix = conf.ldap.suffix @@ -128,7 +129,8 @@ class BaseLdap(object): if self.LDAP_URL.startswith('fake://'): conn = fakeldap.FakeLdap(self.LDAP_URL) else: - conn = LdapWrapper(self.LDAP_URL) + conn = LdapWrapper(self.LDAP_URL, + self.page_size) if user is None: user = self.LDAP_USER @@ -361,9 +363,10 @@ class BaseLdap(object): class LdapWrapper(object): - def __init__(self, url): + def __init__(self, url, page_size): LOG.debug(_("LDAP init: url=%s"), url) self.conn = ldap.initialize(url) + self.page_size = page_size def simple_bind_s(self, user, password): LOG.debug(_("LDAP bind: dn=%s"), user) @@ -387,15 +390,59 @@ class LdapWrapper(object): scope, query, attrlist) - res = self.conn.search_s(dn, scope, query, attrlist) + if self.page_size: + res = self.paged_search_s(dn, scope, query, attrlist) + else: + res = self.conn.search_s(dn, scope, query, attrlist) o = [] for dn, attrs in res: o.append((dn, dict((kind, [ldap2py(x) for x in values]) for kind, values in attrs.iteritems()))) - return o + def paged_search_s(self, dn, scope, query, attrlist=None): + res = [] + lc = ldap.controls.SimplePagedResultsControl( + controlType=ldap.LDAP_CONTROL_PAGE_OID, + criticality=True, + controlValue=(self.page_size, '')) + msgid = self.conn.search_ext(dn, + scope, + query, + attrlist, + serverctrls=[lc]) + # Endless loop request pages on ldap server until it has no data + while True: + # Request to the ldap server a page with 'page_size' entries + rtype, rdata, rmsgid, serverctrls = self.conn.result3(msgid) + # Receive the data + res.extend(rdata) + pctrls = [c for c in serverctrls + if c.controlType == ldap.LDAP_CONTROL_PAGE_OID] + if pctrls: + # LDAP server supports pagination + est, cookie = pctrls[0].controlValue + if cookie: + # There is more data still on the server + # so we request another page + lc.controlValue = (self.page_size, cookie) + msgid = self.conn.search_ext(dn, + scope, + query, + attrlist, + serverctrls=[lc]) + else: + # Exit condition no more data on server + break + else: + LOG.warning(_('LDAP Server does not support paging.' + 'Disable paging in keystone.conf to' + 'avoid this message')) + self._disable_paging() + break + return res + def modify_s(self, dn, modlist): ldap_modlist = [ (op, kind, (None if values is None @@ -418,6 +465,10 @@ class LdapWrapper(object): LOG.debug(_("LDAP delete_ext: dn=%s, serverctrls=%s"), dn, serverctrls) return self.conn.delete_ext_s(dn, serverctrls) + def _disable_paging(self): + # Disable the pagination from now on + self.page_size = 0 + class EnabledEmuMixIn(BaseLdap): """Emulates boolean 'enabled' attribute if turned on. diff --git a/keystone/config.py b/keystone/config.py index b67d477a0a..eca692f116 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -250,6 +250,7 @@ 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_int('page_size', group='ldap', default=0) register_str('user_tree_dn', group='ldap', default=None) register_str('user_filter', group='ldap', default=None)