summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.zuul.yaml4
-rw-r--r--keystone/identity/backends/ldap/common.py118
-rw-r--r--keystone/tests/unit/identity/backends/test_ldap_common.py14
-rw-r--r--lower-constraints.txt2
-rw-r--r--releasenotes/notes/bug-1794527-866b1caff67977f3.yaml2
-rw-r--r--releasenotes/notes/bug-1889936-78d6853b5212b8f1.yaml5
-rw-r--r--releasenotes/notes/bug-1896125-b17a4d12730fe493.yaml7
7 files changed, 108 insertions, 44 deletions
diff --git a/.zuul.yaml b/.zuul.yaml
index 8fa6bb277..edeca5efa 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -176,7 +176,6 @@
- project:
templates:
- openstack-cover-jobs
- - openstack-lower-constraints-jobs
- openstack-python-jobs
- openstack-python3-train-jobs
- publish-openstack-docs-pti
@@ -204,6 +203,7 @@
voting: false
irrelevant-files: *irrelevant-files
- keystone-dsvm-py3-functional-federation-opensuse15-k2k:
+ voting: false
irrelevant-files: *irrelevant-files
- keystoneclient-devstack-functional:
voting: false
@@ -236,8 +236,6 @@
irrelevant-files: *irrelevant-files
- keystone-dsvm-py3-functional:
irrelevant-files: *irrelevant-files
- - keystone-dsvm-py3-functional-federation-opensuse15-k2k:
- irrelevant-files: *irrelevant-files
- tempest-full:
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3:
diff --git a/keystone/identity/backends/ldap/common.py b/keystone/identity/backends/ldap/common.py
index cef0d9bdf..879da2986 100644
--- a/keystone/identity/backends/ldap/common.py
+++ b/keystone/identity/backends/ldap/common.py
@@ -14,10 +14,10 @@
import abc
import codecs
-import functools
import os.path
import re
import sys
+import uuid
import weakref
import ldap.controls
@@ -95,7 +95,16 @@ def utf8_decode(value):
:raises UnicodeDecodeError: for invalid UTF-8 encoding
"""
if isinstance(value, six.binary_type):
- return _utf8_decoder(value)[0]
+ try:
+ return _utf8_decoder(value)[0]
+ except UnicodeDecodeError:
+ # NOTE(lbragstad): We could be dealing with a UUID in byte form,
+ # which some LDAP implementations use.
+ uuid_byte_string_length = 16
+ if len(value) == uuid_byte_string_length:
+ return six.text_type(uuid.UUID(bytes_le=value))
+ else:
+ raise
return six.text_type(value)
@@ -635,10 +644,36 @@ def _common_ldap_initialization(url, use_tls=False, tls_cacertfile=None,
tls_req_cert)
-class MsgId(list):
- """Wrapper class to hold connection and msgid."""
+class AsynchronousMessage(object):
+ """A container for handling asynchronous LDAP responses.
+
+ Some LDAP APIs, like `search_ext`, are asynchronous and return a message ID
+ when the server successfully initiates the operation. Clients can use this
+ message ID and the original connection to make the request to fetch the
+ results using `result3`.
+
+ This object holds the message ID, the original connection, and a callable
+ weak reference Finalizer that cleans up context managers specific to the
+ connection associated to the message ID.
+
+ :param message_id: The message identifier (str).
+ :param connection: The connection associated with the message identifier
+ (ldappool.StateConnector).
+
+ The `clean` attribute is a callable that cleans up the context manager used
+ to create or return the connection object (weakref.finalize).
- pass
+ """
+
+ def __init__(self, message_id, connection, context_manager):
+ self.id = message_id
+ self.connection = connection
+ self.clean = weakref.finalize(
+ self, self._cleanup_connection_context_manager, context_manager
+ )
+
+ def _cleanup_connection_context_manager(self, context_manager):
+ context_manager.__exit__(None, None, None)
def use_conn_pool(func):
@@ -792,15 +827,17 @@ class PooledLDAPHandler(LDAPHandler):
filterstr='(objectClass=*)', attrlist=None, attrsonly=0,
serverctrls=None, clientctrls=None,
timeout=-1, sizelimit=0):
- """Return a ``MsgId`` instance, it asynchronous API.
+ """Return an AsynchronousMessage instance, it asynchronous API.
- The ``MsgId`` instance can be safely used in a call to ``result3()``.
+ The AsynchronousMessage instance can be safely used in a call to
+ `result3()`.
- To work with ``result3()`` API in predictable manner, the same LDAP
- connection is needed which originally provided the ``msgid``. So, this
- method wraps the existing connection and ``msgid`` in a new ``MsgId``
- instance. The connection associated with ``search_ext`` is released
- once last hard reference to the ``MsgId`` instance is freed.
+ To work with `result3()` API in predictable manner, the same LDAP
+ connection is needed which originally provided the `msgid`. So, this
+ method wraps the existing connection and `msgid` in a new
+ `AsynchronousMessage` instance. The connection associated with
+ `search_ext()` is released after `result3()` fetches the data
+ associated with `msgid`.
"""
conn_ctxt = self._get_pool_connection()
@@ -813,30 +850,33 @@ class PooledLDAPHandler(LDAPHandler):
except Exception:
conn_ctxt.__exit__(*sys.exc_info())
raise
- res = MsgId((conn, msgid))
- weakref.ref(res, functools.partial(conn_ctxt.__exit__,
- None, None, None))
- return res
+ return AsynchronousMessage(msgid, conn, conn_ctxt)
- def result3(self, msgid, all=1, timeout=None,
+ def result3(self, message, all=1, timeout=None,
resp_ctrl_classes=None):
- """Wait for and return the result.
+ """Wait for and return the result to an asynchronous message.
This method returns the result of an operation previously initiated by
- one of the LDAP asynchronous operation routines (eg search_ext()). It
- returned an invocation identifier (a message id) upon successful
- initiation of their operation.
+ one of the LDAP asynchronous operation routines (e.g., `search_ext()`).
+ The `search_ext()` method in python-ldap returns an invocation
+ identifier, or a message ID, upon successful initiation of the
+ operation by the LDAP server.
- Input msgid is expected to be instance of class MsgId which has LDAP
- session/connection used to execute search_ext and message idenfier.
+ The `message` is expected to be instance of class
+ `AsynchronousMessage`, which contains the message ID and the connection
+ used to make the original request.
- The connection associated with search_ext is released once last hard
- reference to MsgId object is freed. This will happen when function
- which requested msgId and used it in result3 exits.
+ The connection and context manager associated with `search_ext()` are
+ cleaned up when message.clean() is called.
"""
- conn, msg_id = msgid
- return conn.result3(msg_id, all, timeout)
+ results = message.connection.result3(message.id, all, timeout)
+
+ # Now that we have the results from the LDAP server for the message, we
+ # don't need the the context manager used to create the connection.
+ message.clean()
+
+ return results
@use_conn_pool
def modify_s(self, conn, dn, modlist):
@@ -997,15 +1037,15 @@ class KeystoneLDAPHandler(LDAPHandler):
cookie='')
page_ctrl_oid = ldap.controls.SimplePagedResultsControl.controlType
- msgid = self.conn.search_ext(base,
- scope,
- filterstr,
- attrlist,
- serverctrls=[lc])
+ message = self.conn.search_ext(base,
+ scope,
+ filterstr,
+ 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)
+ rtype, rdata, rmsgid, serverctrls = self.conn.result3(message)
# Receive the data
res.extend(rdata)
pctrls = [c for c in serverctrls
@@ -1021,11 +1061,11 @@ class KeystoneLDAPHandler(LDAPHandler):
if cookie:
# There is more data still on the server
# so we request another page
- msgid = self.conn.search_ext(base,
- scope,
- filterstr,
- attrlist,
- serverctrls=[lc])
+ message = self.conn.search_ext(base,
+ scope,
+ filterstr,
+ attrlist,
+ serverctrls=[lc])
else:
# Exit condition no more data on server
break
diff --git a/keystone/tests/unit/identity/backends/test_ldap_common.py b/keystone/tests/unit/identity/backends/test_ldap_common.py
index e464a8a14..2a0d9ab28 100644
--- a/keystone/tests/unit/identity/backends/test_ldap_common.py
+++ b/keystone/tests/unit/identity/backends/test_ldap_common.py
@@ -520,6 +520,20 @@ class CommonLdapTestCase(unit.BaseTestCase):
# The user name should still be a string value.
self.assertEqual(user_name, py_result[0][1]['user_name'][0])
+ def test_user_id_attribute_is_uuid_in_byte_form(self):
+ results = [(
+ 'cn=alice,dc=example,dc=com',
+ {
+ 'cn': [b'cn=alice'],
+ 'objectGUID': [b'\xdd\xd8Rt\xee]bA\x8e(\xe39\x0b\xe1\xf8\xe8'],
+ 'email': [uuid.uuid4().hex],
+ 'sn': [uuid.uuid4().hex]
+ }
+ )]
+ py_result = common_ldap.convert_ldap_result(results)
+ exp_object_guid = '7452d8dd-5dee-4162-8e28-e3390be1f8e8'
+ self.assertEqual(exp_object_guid, py_result[0][1]['objectGUID'][0])
+
class LDAPFilterQueryCompositionTest(unit.BaseTestCase):
"""These test cases test LDAP filter generation."""
diff --git a/lower-constraints.txt b/lower-constraints.txt
index 6ccbdc230..a6fa74847 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -1,4 +1,4 @@
-amqp==2.2.2
+amqp==5.0.0
Babel==2.3.4
bashate==0.5.1
bcrypt==3.1.3
diff --git a/releasenotes/notes/bug-1794527-866b1caff67977f3.yaml b/releasenotes/notes/bug-1794527-866b1caff67977f3.yaml
index 6a8134fd2..04435288d 100644
--- a/releasenotes/notes/bug-1794527-866b1caff67977f3.yaml
+++ b/releasenotes/notes/bug-1794527-866b1caff67977f3.yaml
@@ -18,4 +18,4 @@ features:
the old one, and having spurious failures, or undesirecd domain_id
matching.
- https://specs.openstack.org/openstack/keystone-specs/specs/keystone/stein/explicit-domains-ids.html \ No newline at end of file
+ https://specs.openstack.org/openstack/keystone-specs/specs/keystone/train/explicit-domains-ids.html
diff --git a/releasenotes/notes/bug-1889936-78d6853b5212b8f1.yaml b/releasenotes/notes/bug-1889936-78d6853b5212b8f1.yaml
new file mode 100644
index 000000000..de96b27f7
--- /dev/null
+++ b/releasenotes/notes/bug-1889936-78d6853b5212b8f1.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+ - |
+ [`bug 1889936 <https://bugs.launchpad.net/keystone/+bug/1889936>`_]
+ Properly decode octet strings, or byte arrays, returned from LDAP.
diff --git a/releasenotes/notes/bug-1896125-b17a4d12730fe493.yaml b/releasenotes/notes/bug-1896125-b17a4d12730fe493.yaml
new file mode 100644
index 000000000..7a758b9ab
--- /dev/null
+++ b/releasenotes/notes/bug-1896125-b17a4d12730fe493.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+ - |
+ [`bug 1896125 <https://bugs.launchpad.net/keystone/+bug/1896125>`_]
+ Introduced more robust connection handling for asynchronous LDAP requests
+ to address memory leaks fetching data from LDAP backends with low page
+ sizes.