From 6133f388f73b21b931462b917c49a8e175b35aa4 Mon Sep 17 00:00:00 2001
From: Nathan Kinder <nkinder@redhat.com>
Date: Wed, 15 Oct 2014 15:39:55 -0700
Subject: [PATCH] Use newer python-ldap paging control API
The API for using the LDAP simple paged results control changed
between python-ldap version 2.3 and 2.4. Our current implementation
fails with an AttributeError when trying to use paging with version
2.4 of python-ldap.
This patch detects the capabilities of the underlying python-ldap
version and uses the newer API in versions of python-ldap that have
removed the older API.
Change-Id: I2986e12daea3edf50f299af5927d2a05278e82f7
Closes-bug: #1381768
(cherry picked from commit 1be4a15454e6917571bc937e3bb3589e8f79bc55)
(cherry picked from commit db291b340e63b74d8d240abfc37d03fb163f33f1)
---
keystone/common/ldap/core.py | 32 +++++++++++++++++++------
keystone/tests/unit/common/test_ldap.py | 42 +++++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+), 7 deletions(-)
diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py
index 3267502..2da70e3 100644
--- a/keystone/common/ldap/core.py
+++ b/keystone/common/ldap/core.py
@@ -960,10 +960,24 @@ class KeystoneLDAPHandler(LDAPHandler):
def _paged_search_s(self, base, scope, filterstr, attrlist=None):
res = []
- lc = ldap.controls.SimplePagedResultsControl(
- controlType=ldap.LDAP_CONTROL_PAGE_OID,
- criticality=True,
- controlValue=(self.page_size, ''))
+ use_old_paging_api = False
+ # The API for the simple paged results control changed between
+ # python-ldap 2.3 and 2.4. We need to detect the capabilities
+ # of the python-ldap version we are using.
+ if hasattr(ldap, 'LDAP_CONTROL_PAGE_OID'):
+ use_old_paging_api = True
+ lc = ldap.controls.SimplePagedResultsControl(
+ controlType=ldap.LDAP_CONTROL_PAGE_OID,
+ criticality=True,
+ controlValue=(self.page_size, ''))
+ page_ctrl_oid = ldap.LDAP_CONTROL_PAGE_OID
+ else:
+ lc = ldap.controls.libldap.SimplePagedResultsControl(
+ criticality=True,
+ size=self.page_size,
+ cookie='')
+ page_ctrl_oid = ldap.controls.SimplePagedResultsControl.controlType
+
base_utf8 = utf8_encode(base)
filterstr_utf8 = utf8_encode(filterstr)
if attrlist is None:
@@ -983,14 +997,18 @@ class KeystoneLDAPHandler(LDAPHandler):
# Receive the data
res.extend(rdata)
pctrls = [c for c in serverctrls
- if c.controlType == ldap.LDAP_CONTROL_PAGE_OID]
+ if c.controlType == page_ctrl_oid]
if pctrls:
# LDAP server supports pagination
- est, cookie = pctrls[0].controlValue
+ if use_old_paging_api:
+ est, cookie = pctrls[0].controlValue
+ lc.controlValue = (self.page_size, cookie)
+ else:
+ cookie = lc.cookie = pctrls[0].cookie
+
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(base_utf8,
scope,
filterstr_utf8,
diff --git a/keystone/tests/unit/common/test_ldap.py b/keystone/tests/unit/common/test_ldap.py
index 61f2fd1..b3e70c9 100644
--- a/keystone/tests/unit/common/test_ldap.py
+++ b/keystone/tests/unit/common/test_ldap.py
@@ -349,3 +349,45 @@ class SslTlsTest(tests.TestCase):
# Ensure the cert trust option is set.
self.assertEqual(certdir, ldap.get_option(ldap.OPT_X_TLS_CACERTDIR))
+
+
+class LDAPPagedResultsTest(tests.TestCase):
+ """Tests the paged results functionality in keystone.common.ldap.core."""
+
+ def setUp(self):
+ super(LDAPPagedResultsTest, self).setUp()
+ self.clear_database()
+
+ ks_ldap.register_handler('fake://', fakeldap.FakeLdap)
+ self.addCleanup(common_ldap_core._HANDLERS.clear)
+
+ self.load_backends()
+ self.load_fixtures(default_fixtures)
+
+ def clear_database(self):
+ for shelf in fakeldap.FakeShelves:
+ fakeldap.FakeShelves[shelf].clear()
+
+ def config_overrides(self):
+ super(LDAPPagedResultsTest, self).config_overrides()
+ self.config_fixture.config(
+ group='identity',
+ driver='keystone.identity.backends.ldap.Identity')
+
+ def config_files(self):
+ config_files = super(LDAPPagedResultsTest, self).config_files()
+ config_files.append(tests.dirs.tests_conf('backend_ldap.conf'))
+ return config_files
+
+ @mock.patch.object(fakeldap.FakeLdap, 'search_ext')
+ @mock.patch.object(fakeldap.FakeLdap, 'result3')
+ def test_paged_results_control_api(self, mock_result3, mock_search_ext):
+ mock_result3.return_value = ('', [], 1, [])
+
+ self.config_fixture.config(group='ldap',
+ page_size=1)
+
+ conn = self.identity_api.user.get_connection()
+ conn._paged_search_s('dc=example,dc=test',
+ ldap.SCOPE_SUBTREE,
+ 'objectclass=*')