PY3: switch to using unicode text values
In Python 3, python-ldap no longer allows bytes for some fields (DNs, RDNs, attribute names, queries). Instead, text values are represented as str, the Unicode text type. Compatibility support is provided for Python 2 by setting bytes_mode=False [1]. Update the keystone LDAP backend to adhere to this behavior by using bytes_mode=False for Python 2 and dropping UTF-8 encoding and decoding fields that are now represented as text in python-ldap. [1] More details about byte/str usage in python-ldap can be found at: http://www.python-ldap.org/en/latest/bytes_mode.html#bytes-mode Note that at a minimum python-ldappool 2.3.1 is required. For more details see Depends-On's below. Change-Id: Ifdd0644cd7042407a008c85c0b2c40a971c90bc3 Closes-Bug: #1798184 Depends-On: https://review.openstack.org/611401 Depends-On: https://review.openstack.org/613632 Depends-On: https://review.openstack.org/614052
This commit is contained in:
parent
f81afc7ce6
commit
eca0829c4c
|
@ -27,6 +27,7 @@ from oslo_log import log
|
||||||
from oslo_utils import reflection
|
from oslo_utils import reflection
|
||||||
import six
|
import six
|
||||||
from six.moves import map, zip
|
from six.moves import map, zip
|
||||||
|
from six import PY2
|
||||||
|
|
||||||
from keystone.common import driver_hints
|
from keystone.common import driver_hints
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
|
@ -152,8 +153,9 @@ def convert_ldap_result(ldap_result):
|
||||||
are lists of strings.
|
are lists of strings.
|
||||||
|
|
||||||
OpenStack wants to use Python types of its choosing. Strings will
|
OpenStack wants to use Python types of its choosing. Strings will
|
||||||
be unicode, truth values boolean, whole numbers int's, etc. DN's will
|
be unicode, truth values boolean, whole numbers int's, etc. DN's are
|
||||||
also be decoded from UTF-8 to unicode.
|
represented as text in python-ldap by default for Python 3 and when
|
||||||
|
bytes_mode=False for Python 2, and therefore do not require decoding.
|
||||||
|
|
||||||
:param ldap_result: LDAP search result
|
:param ldap_result: LDAP search result
|
||||||
:returns: list of 2-tuples containing (dn, attrs) where dn is unicode
|
:returns: list of 2-tuples containing (dn, attrs) where dn is unicode
|
||||||
|
@ -175,8 +177,7 @@ def convert_ldap_result(ldap_result):
|
||||||
ldap_attrs[kind] = [val2py(x) for x in values]
|
ldap_attrs[kind] = [val2py(x) for x in values]
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
LOG.debug('Unable to decode value for attribute %s', kind)
|
LOG.debug('Unable to decode value for attribute %s', kind)
|
||||||
|
py_result.append((dn, ldap_attrs))
|
||||||
py_result.append((utf8_decode(dn), ldap_attrs))
|
|
||||||
if at_least_one_referral:
|
if at_least_one_referral:
|
||||||
LOG.debug('Referrals were returned and ignored. Enable referral '
|
LOG.debug('Referrals were returned and ignored. Enable referral '
|
||||||
'chasing in keystone.conf via [ldap] chase_referrals')
|
'chasing in keystone.conf via [ldap] chase_referrals')
|
||||||
|
@ -299,9 +300,9 @@ def is_dn_equal(dn1, dn2):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(dn1, list):
|
if not isinstance(dn1, list):
|
||||||
dn1 = ldap.dn.str2dn(utf8_encode(dn1))
|
dn1 = ldap.dn.str2dn(dn1)
|
||||||
if not isinstance(dn2, list):
|
if not isinstance(dn2, list):
|
||||||
dn2 = ldap.dn.str2dn(utf8_encode(dn2))
|
dn2 = ldap.dn.str2dn(dn2)
|
||||||
|
|
||||||
if len(dn1) != len(dn2):
|
if len(dn1) != len(dn2):
|
||||||
return False
|
return False
|
||||||
|
@ -320,9 +321,9 @@ def dn_startswith(descendant_dn, dn):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(descendant_dn, list):
|
if not isinstance(descendant_dn, list):
|
||||||
descendant_dn = ldap.dn.str2dn(utf8_encode(descendant_dn))
|
descendant_dn = ldap.dn.str2dn(descendant_dn)
|
||||||
if not isinstance(dn, list):
|
if not isinstance(dn, list):
|
||||||
dn = ldap.dn.str2dn(utf8_encode(dn))
|
dn = ldap.dn.str2dn(dn)
|
||||||
|
|
||||||
if len(descendant_dn) <= len(dn):
|
if len(descendant_dn) <= len(dn):
|
||||||
return False
|
return False
|
||||||
|
@ -345,6 +346,11 @@ class LDAPHandler(object):
|
||||||
|
|
||||||
* unicode strings are encoded in UTF-8
|
* unicode strings are encoded in UTF-8
|
||||||
|
|
||||||
|
Note, in python-ldap some fields (DNs, RDNs, attribute names, queries)
|
||||||
|
are represented as text (str on Python 3, unicode on Python 2 when
|
||||||
|
bytes_mode=False). For more details see:
|
||||||
|
http://www.python-ldap.org/en/latest/bytes_mode.html#bytes-mode
|
||||||
|
|
||||||
In addition to handling type conversions at the API boundary we
|
In addition to handling type conversions at the API boundary we
|
||||||
have the requirement to support more than one LDAP API
|
have the requirement to support more than one LDAP API
|
||||||
provider. Currently we have:
|
provider. Currently we have:
|
||||||
|
@ -482,7 +488,14 @@ class LDAPHandler(object):
|
||||||
class PythonLDAPHandler(LDAPHandler):
|
class PythonLDAPHandler(LDAPHandler):
|
||||||
"""LDAPHandler implementation which calls the python-ldap API.
|
"""LDAPHandler implementation which calls the python-ldap API.
|
||||||
|
|
||||||
Note, the python-ldap API requires all string values to be UTF-8 encoded.
|
Note, the python-ldap API requires all string attribute values to be UTF-8
|
||||||
|
encoded.
|
||||||
|
|
||||||
|
Note, in python-ldap some fields (DNs, RDNs, attribute names, queries)
|
||||||
|
are represented as text (str on Python 3, unicode on Python 2 when
|
||||||
|
bytes_mode=False). For more details see:
|
||||||
|
http://www.python-ldap.org/en/latest/bytes_mode.html#bytes-mode
|
||||||
|
|
||||||
The KeystoneLDAPHandler enforces this prior to invoking the methods in this
|
The KeystoneLDAPHandler enforces this prior to invoking the methods in this
|
||||||
class.
|
class.
|
||||||
|
|
||||||
|
@ -503,7 +516,14 @@ class PythonLDAPHandler(LDAPHandler):
|
||||||
debug_level=debug_level,
|
debug_level=debug_level,
|
||||||
timeout=conn_timeout)
|
timeout=conn_timeout)
|
||||||
|
|
||||||
self.conn = ldap.initialize(url)
|
if PY2:
|
||||||
|
# NOTE: Once https://github.com/python-ldap/python-ldap/issues/249
|
||||||
|
# is released, we can pass bytes_strictness='warn' as a parameter
|
||||||
|
# to ldap.initialize instead of setting it after ldap.initialize.
|
||||||
|
self.conn = ldap.initialize(url, bytes_mode=False)
|
||||||
|
self.conn.bytes_strictness = 'warn'
|
||||||
|
else:
|
||||||
|
self.conn = ldap.initialize(url)
|
||||||
self.conn.protocol_version = ldap.VERSION3
|
self.conn.protocol_version = ldap.VERSION3
|
||||||
|
|
||||||
if alias_dereferencing is not None:
|
if alias_dereferencing is not None:
|
||||||
|
@ -653,9 +673,14 @@ class PooledLDAPHandler(LDAPHandler):
|
||||||
If 'use_auth_pool' is not enabled, then connection pooling is not used for
|
If 'use_auth_pool' is not enabled, then connection pooling is not used for
|
||||||
those LDAP operations.
|
those LDAP operations.
|
||||||
|
|
||||||
Note, the python-ldap API requires all string values to be UTF-8
|
Note, the python-ldap API requires all string attribute values to be UTF-8
|
||||||
encoded. The KeystoneLDAPHandler enforces this prior to invoking
|
encoded. The KeystoneLDAPHandler enforces this prior to invoking the
|
||||||
the methods in this class.
|
methods in this class.
|
||||||
|
|
||||||
|
Note, in python-ldap some fields (DNs, RDNs, attribute names, queries)
|
||||||
|
are represented as text (str on Python 3, unicode on Python 2 when
|
||||||
|
bytes_mode=False). For more details see:
|
||||||
|
http://www.python-ldap.org/en/latest/bytes_mode.html#bytes-mode
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -822,11 +847,16 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
"""Convert data types and perform logging.
|
"""Convert data types and perform logging.
|
||||||
|
|
||||||
This LDAP interface wraps the python-ldap based interfaces. The
|
This LDAP interface wraps the python-ldap based interfaces. The
|
||||||
python-ldap interfaces require string values encoded in UTF-8. The
|
python-ldap interfaces require string values encoded in UTF-8 with
|
||||||
OpenStack logging framework at the time of this writing is not
|
the exception of [1]. The OpenStack logging framework at the time
|
||||||
capable of accepting strings encoded in UTF-8, the log functions
|
of this writing is not capable of accepting strings encoded in
|
||||||
will throw decoding errors if a non-ascii character appears in a
|
UTF-8, the log functions will throw decoding errors if a non-ascii
|
||||||
string.
|
character appears in a string.
|
||||||
|
|
||||||
|
[1] In python-ldap, some fields (DNs, RDNs, attribute names,
|
||||||
|
queries) are represented as text (str on Python 3, unicode on
|
||||||
|
Python 2 when bytes_mode=False). For more details see:
|
||||||
|
http://www.python-ldap.org/en/latest/bytes_mode.html#bytes-mode
|
||||||
|
|
||||||
Prior to the call Python data types are converted to a string
|
Prior to the call Python data types are converted to a string
|
||||||
representation as required by the LDAP APIs.
|
representation as required by the LDAP APIs.
|
||||||
|
@ -885,9 +915,7 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
def simple_bind_s(self, who='', cred='',
|
def simple_bind_s(self, who='', cred='',
|
||||||
serverctrls=None, clientctrls=None):
|
serverctrls=None, clientctrls=None):
|
||||||
LOG.debug('LDAP bind: who=%s', who)
|
LOG.debug('LDAP bind: who=%s', who)
|
||||||
who_utf8 = utf8_encode(who)
|
return self.conn.simple_bind_s(who, cred,
|
||||||
cred_utf8 = utf8_encode(cred)
|
|
||||||
return self.conn.simple_bind_s(who_utf8, cred_utf8,
|
|
||||||
serverctrls=serverctrls,
|
serverctrls=serverctrls,
|
||||||
clientctrls=clientctrls)
|
clientctrls=clientctrls)
|
||||||
|
|
||||||
|
@ -904,10 +932,9 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
for kind, values in ldap_attrs]
|
for kind, values in ldap_attrs]
|
||||||
LOG.debug('LDAP add: dn=%s attrs=%s',
|
LOG.debug('LDAP add: dn=%s attrs=%s',
|
||||||
dn, logging_attrs)
|
dn, logging_attrs)
|
||||||
dn_utf8 = utf8_encode(dn)
|
|
||||||
ldap_attrs_utf8 = [(kind, [utf8_encode(x) for x in safe_iter(values)])
|
ldap_attrs_utf8 = [(kind, [utf8_encode(x) for x in safe_iter(values)])
|
||||||
for kind, values in ldap_attrs]
|
for kind, values in ldap_attrs]
|
||||||
return self.conn.add_s(dn_utf8, ldap_attrs_utf8)
|
return self.conn.add_s(dn, ldap_attrs_utf8)
|
||||||
|
|
||||||
def search_s(self, base, scope,
|
def search_s(self, base, scope,
|
||||||
filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
|
filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
|
||||||
|
@ -924,15 +951,12 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
ldap_result = self._paged_search_s(base, scope,
|
ldap_result = self._paged_search_s(base, scope,
|
||||||
filterstr, attrlist)
|
filterstr, attrlist)
|
||||||
else:
|
else:
|
||||||
base_utf8 = utf8_encode(base)
|
|
||||||
filterstr_utf8 = utf8_encode(filterstr)
|
|
||||||
if attrlist is None:
|
if attrlist is None:
|
||||||
attrlist_utf8 = None
|
attrlist_utf8 = None
|
||||||
else:
|
else:
|
||||||
attrlist_utf8 = list(map(utf8_encode, attrlist))
|
attrlist_utf8 = list(map(utf8_encode, attrlist))
|
||||||
try:
|
try:
|
||||||
ldap_result = self.conn.search_s(base_utf8, scope,
|
ldap_result = self.conn.search_s(base, scope, filterstr,
|
||||||
filterstr_utf8,
|
|
||||||
attrlist_utf8, attrsonly)
|
attrlist_utf8, attrsonly)
|
||||||
except ldap.SIZELIMIT_EXCEEDED:
|
except ldap.SIZELIMIT_EXCEEDED:
|
||||||
raise exception.LDAPSizeLimitExceeded()
|
raise exception.LDAPSizeLimitExceeded()
|
||||||
|
@ -977,16 +1001,14 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
cookie='')
|
cookie='')
|
||||||
page_ctrl_oid = ldap.controls.SimplePagedResultsControl.controlType
|
page_ctrl_oid = ldap.controls.SimplePagedResultsControl.controlType
|
||||||
|
|
||||||
base_utf8 = utf8_encode(base)
|
|
||||||
filterstr_utf8 = utf8_encode(filterstr)
|
|
||||||
if attrlist is None:
|
if attrlist is None:
|
||||||
attrlist_utf8 = None
|
attrlist_utf8 = None
|
||||||
else:
|
else:
|
||||||
attrlist = [attr for attr in attrlist if attr is not None]
|
attrlist = [attr for attr in attrlist if attr is not None]
|
||||||
attrlist_utf8 = list(map(utf8_encode, attrlist))
|
attrlist_utf8 = list(map(utf8_encode, attrlist))
|
||||||
msgid = self.conn.search_ext(base_utf8,
|
msgid = self.conn.search_ext(base,
|
||||||
scope,
|
scope,
|
||||||
filterstr_utf8,
|
filterstr,
|
||||||
attrlist_utf8,
|
attrlist_utf8,
|
||||||
serverctrls=[lc])
|
serverctrls=[lc])
|
||||||
# Endless loop request pages on ldap server until it has no data
|
# Endless loop request pages on ldap server until it has no data
|
||||||
|
@ -1008,9 +1030,9 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
if cookie:
|
if cookie:
|
||||||
# There is more data still on the server
|
# There is more data still on the server
|
||||||
# so we request another page
|
# so we request another page
|
||||||
msgid = self.conn.search_ext(base_utf8,
|
msgid = self.conn.search_ext(base,
|
||||||
scope,
|
scope,
|
||||||
filterstr_utf8,
|
filterstr,
|
||||||
attrlist_utf8,
|
attrlist_utf8,
|
||||||
serverctrls=[lc])
|
serverctrls=[lc])
|
||||||
else:
|
else:
|
||||||
|
@ -1051,12 +1073,11 @@ class KeystoneLDAPHandler(LDAPHandler):
|
||||||
LOG.debug('LDAP modify: dn=%s modlist=%s',
|
LOG.debug('LDAP modify: dn=%s modlist=%s',
|
||||||
dn, logging_modlist)
|
dn, logging_modlist)
|
||||||
|
|
||||||
dn_utf8 = utf8_encode(dn)
|
|
||||||
ldap_modlist_utf8 = [
|
ldap_modlist_utf8 = [
|
||||||
(op, kind, (None if values is None
|
(op, kind, (None if values is None
|
||||||
else [utf8_encode(x) for x in safe_iter(values)]))
|
else [utf8_encode(x) for x in safe_iter(values)]))
|
||||||
for op, kind, values in ldap_modlist]
|
for op, kind, values in ldap_modlist]
|
||||||
return self.conn.modify_s(dn_utf8, ldap_modlist_utf8)
|
return self.conn.modify_s(dn, ldap_modlist_utf8)
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
"""Exit runtime context, unbind LDAP."""
|
"""Exit runtime context, unbind LDAP."""
|
||||||
|
@ -1283,7 +1304,7 @@ class BaseLdap(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _dn_to_id(dn):
|
def _dn_to_id(dn):
|
||||||
return utf8_decode(ldap.dn.str2dn(utf8_encode(dn))[0][0][1])
|
return ldap.dn.str2dn(dn)[0][0][1]
|
||||||
|
|
||||||
def _ldap_res_to_model(self, res):
|
def _ldap_res_to_model(self, res):
|
||||||
# LDAP attribute names may be returned in a different case than
|
# LDAP attribute names may be returned in a different case than
|
||||||
|
@ -1769,10 +1790,10 @@ class EnabledEmuMixIn(BaseLdap):
|
||||||
naming_attr = (naming_attr_name, [naming_attr_value])
|
naming_attr = (naming_attr_name, [naming_attr_value])
|
||||||
else:
|
else:
|
||||||
# Extract the attribute name and value from the configured DN.
|
# Extract the attribute name and value from the configured DN.
|
||||||
naming_dn = ldap.dn.str2dn(utf8_encode(self.enabled_emulation_dn))
|
naming_dn = ldap.dn.str2dn(self.enabled_emulation_dn)
|
||||||
naming_rdn = naming_dn[0][0]
|
naming_rdn = naming_dn[0][0]
|
||||||
naming_attr = (utf8_decode(naming_rdn[0]),
|
naming_attr = (naming_rdn[0],
|
||||||
utf8_decode(naming_rdn[1]))
|
naming_rdn[1])
|
||||||
self.enabled_emulation_naming_attr = naming_attr
|
self.enabled_emulation_naming_attr = naming_attr
|
||||||
|
|
||||||
def _get_enabled(self, object_id, conn):
|
def _get_enabled(self, object_id, conn):
|
||||||
|
|
|
@ -65,7 +65,7 @@ def _internal_attr(attr_name, value_or_values):
|
||||||
return 'CN=Doe\\2C John,OU=Users,CN=example,CN=com'
|
return 'CN=Doe\\2C John,OU=Users,CN=example,CN=com'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dn = ldap.dn.str2dn(common.utf8_encode(dn))
|
dn = ldap.dn.str2dn(dn)
|
||||||
except ldap.DECODING_ERROR:
|
except ldap.DECODING_ERROR:
|
||||||
# NOTE(amakarov): In case of IDs instead of DNs in group members
|
# NOTE(amakarov): In case of IDs instead of DNs in group members
|
||||||
# they must be handled as regular values.
|
# they must be handled as regular values.
|
||||||
|
@ -74,10 +74,9 @@ def _internal_attr(attr_name, value_or_values):
|
||||||
norm = []
|
norm = []
|
||||||
for part in dn:
|
for part in dn:
|
||||||
name, val, i = part[0]
|
name, val, i = part[0]
|
||||||
name = common.utf8_decode(name)
|
|
||||||
name = name.upper()
|
name = name.upper()
|
||||||
norm.append([(name, val, i)])
|
norm.append([(name, val, i)])
|
||||||
return common.utf8_decode(ldap.dn.dn2str(norm))
|
return ldap.dn.dn2str(norm)
|
||||||
|
|
||||||
if attr_name in ('member', 'roleOccupant'):
|
if attr_name in ('member', 'roleOccupant'):
|
||||||
attr_fn = normalize_dn
|
attr_fn = normalize_dn
|
||||||
|
@ -216,8 +215,8 @@ PendingRequests = {}
|
||||||
class FakeLdap(common.LDAPHandler):
|
class FakeLdap(common.LDAPHandler):
|
||||||
"""Emulate the python-ldap API.
|
"""Emulate the python-ldap API.
|
||||||
|
|
||||||
The python-ldap API requires all strings to be UTF-8 encoded. This
|
The python-ldap API requires all strings to be UTF-8 encoded with the
|
||||||
is assured by the caller of this interface
|
exception of [1]. This is assured by the caller of this interface
|
||||||
(i.e. KeystoneLDAPHandler).
|
(i.e. KeystoneLDAPHandler).
|
||||||
|
|
||||||
However, internally this emulation MUST process and store strings
|
However, internally this emulation MUST process and store strings
|
||||||
|
@ -228,6 +227,11 @@ class FakeLdap(common.LDAPHandler):
|
||||||
emulation, and encodes them back to UTF-8 when returning values
|
emulation, and encodes them back to UTF-8 when returning values
|
||||||
from the emulation.
|
from the emulation.
|
||||||
|
|
||||||
|
[1] Some fields (DNs, RDNs, attribute names, queries) are represented
|
||||||
|
as text in python-ldap for Python 3, and for Python 2 when
|
||||||
|
bytes_mode=False. For more details see:
|
||||||
|
http://www.python-ldap.org/en/latest/bytes_mode.html#bytes-mode
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__prefix = 'ldap:'
|
__prefix = 'ldap:'
|
||||||
|
@ -278,19 +282,14 @@ class FakeLdap(common.LDAPHandler):
|
||||||
self.pool_conn_lifetime = pool_conn_lifetime
|
self.pool_conn_lifetime = pool_conn_lifetime
|
||||||
self.conn_timeout = conn_timeout
|
self.conn_timeout = conn_timeout
|
||||||
|
|
||||||
def dn(self, dn):
|
|
||||||
return common.utf8_decode(dn)
|
|
||||||
|
|
||||||
def _dn_to_id_attr(self, dn):
|
def _dn_to_id_attr(self, dn):
|
||||||
return common.utf8_decode(
|
return ldap.dn.str2dn(dn)[0][0][0]
|
||||||
ldap.dn.str2dn(common.utf8_encode(dn))[0][0][0])
|
|
||||||
|
|
||||||
def _dn_to_id_value(self, dn):
|
def _dn_to_id_value(self, dn):
|
||||||
return common.utf8_decode(
|
return ldap.dn.str2dn(dn)[0][0][1]
|
||||||
ldap.dn.str2dn(common.utf8_encode(dn))[0][0][1])
|
|
||||||
|
|
||||||
def key(self, dn):
|
def key(self, dn):
|
||||||
return '%s%s' % (self.__prefix, self.dn(dn))
|
return '%s%s' % (self.__prefix, dn)
|
||||||
|
|
||||||
def simple_bind_s(self, who='', cred='',
|
def simple_bind_s(self, who='', cred='',
|
||||||
serverctrls=None, clientctrls=None):
|
serverctrls=None, clientctrls=None):
|
||||||
|
@ -298,27 +297,23 @@ class FakeLdap(common.LDAPHandler):
|
||||||
if server_fail:
|
if server_fail:
|
||||||
raise ldap.SERVER_DOWN
|
raise ldap.SERVER_DOWN
|
||||||
whos = ['cn=Admin', CONF.ldap.user]
|
whos = ['cn=Admin', CONF.ldap.user]
|
||||||
if (common.utf8_decode(who) in whos and
|
if (who in whos and cred in ['password', CONF.ldap.password]):
|
||||||
common.utf8_decode(cred) in ['password', CONF.ldap.password]):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
attrs = self.db.get(self.key(who))
|
attrs = self.db.get(self.key(who))
|
||||||
if not attrs:
|
if not attrs:
|
||||||
LOG.debug('who=%s not found, binding anonymously',
|
LOG.debug('who=%s not found, binding anonymously', who)
|
||||||
common.utf8_decode(who))
|
|
||||||
|
|
||||||
db_password = ''
|
db_password = ''
|
||||||
if attrs:
|
if attrs:
|
||||||
try:
|
try:
|
||||||
db_password = attrs['userPassword'][0]
|
db_password = attrs['userPassword'][0]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
LOG.debug('bind fail: password for who=%s not found',
|
LOG.debug('bind fail: password for who=%s not found', who)
|
||||||
common.utf8_decode(who))
|
|
||||||
raise ldap.INAPPROPRIATE_AUTH
|
raise ldap.INAPPROPRIATE_AUTH
|
||||||
|
|
||||||
if cred != common.utf8_encode(db_password):
|
if cred != db_password:
|
||||||
LOG.debug('bind fail: password for who=%s does not match',
|
LOG.debug('bind fail: password for who=%s does not match', who)
|
||||||
common.utf8_decode(who))
|
|
||||||
raise ldap.INVALID_CREDENTIALS
|
raise ldap.INVALID_CREDENTIALS
|
||||||
|
|
||||||
def unbind_s(self):
|
def unbind_s(self):
|
||||||
|
@ -352,10 +347,9 @@ class FakeLdap(common.LDAPHandler):
|
||||||
raise ldap.NAMING_VIOLATION
|
raise ldap.NAMING_VIOLATION
|
||||||
key = self.key(dn)
|
key = self.key(dn)
|
||||||
LOG.debug('add item: dn=%(dn)s, attrs=%(attrs)s', {
|
LOG.debug('add item: dn=%(dn)s, attrs=%(attrs)s', {
|
||||||
'dn': common.utf8_decode(dn), 'attrs': modlist})
|
'dn': dn, 'attrs': modlist})
|
||||||
if key in self.db:
|
if key in self.db:
|
||||||
LOG.debug('add item failed: dn=%s is already in store.',
|
LOG.debug('add item failed: dn=%s is already in store.', dn)
|
||||||
common.utf8_decode(dn))
|
|
||||||
raise ldap.ALREADY_EXISTS(dn)
|
raise ldap.ALREADY_EXISTS(dn)
|
||||||
|
|
||||||
self.db[key] = {k: _internal_attr(k, v) for k, v in modlist}
|
self.db[key] = {k: _internal_attr(k, v) for k, v in modlist}
|
||||||
|
@ -369,7 +363,7 @@ class FakeLdap(common.LDAPHandler):
|
||||||
return [k for k, v in self.db.items()
|
return [k for k, v in self.db.items()
|
||||||
if re.match('%s.*,%s' % (
|
if re.match('%s.*,%s' % (
|
||||||
re.escape(self.__prefix),
|
re.escape(self.__prefix),
|
||||||
re.escape(self.dn(dn))), k)]
|
re.escape(dn)), k)]
|
||||||
|
|
||||||
def delete_ext_s(self, dn, serverctrls, clientctrls=None):
|
def delete_ext_s(self, dn, serverctrls, clientctrls=None):
|
||||||
"""Remove the ldap object at specified dn."""
|
"""Remove the ldap object at specified dn."""
|
||||||
|
@ -378,11 +372,10 @@ class FakeLdap(common.LDAPHandler):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
key = self.key(dn)
|
key = self.key(dn)
|
||||||
LOG.debug('FakeLdap delete item: dn=%s', common.utf8_decode(dn))
|
LOG.debug('FakeLdap delete item: dn=%s', dn)
|
||||||
del self.db[key]
|
del self.db[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug('delete item failed: dn=%s not found.',
|
LOG.debug('delete item failed: dn=%s not found.', dn)
|
||||||
common.utf8_decode(dn))
|
|
||||||
raise ldap.NO_SUCH_OBJECT
|
raise ldap.NO_SUCH_OBJECT
|
||||||
self.db.sync()
|
self.db.sync()
|
||||||
|
|
||||||
|
@ -398,12 +391,11 @@ class FakeLdap(common.LDAPHandler):
|
||||||
|
|
||||||
key = self.key(dn)
|
key = self.key(dn)
|
||||||
LOG.debug('modify item: dn=%(dn)s attrs=%(attrs)s', {
|
LOG.debug('modify item: dn=%(dn)s attrs=%(attrs)s', {
|
||||||
'dn': common.utf8_decode(dn), 'attrs': modlist})
|
'dn': dn, 'attrs': modlist})
|
||||||
try:
|
try:
|
||||||
entry = self.db[key]
|
entry = self.db[key]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug('modify item failed: dn=%s not found.',
|
LOG.debug('modify item failed: dn=%s not found.', dn)
|
||||||
common.utf8_decode(dn))
|
|
||||||
raise ldap.NO_SUCH_OBJECT
|
raise ldap.NO_SUCH_OBJECT
|
||||||
|
|
||||||
for cmd, k, v in modlist:
|
for cmd, k, v in modlist:
|
||||||
|
@ -483,19 +475,19 @@ class FakeLdap(common.LDAPHandler):
|
||||||
for k, v in self.db.items()
|
for k, v in self.db.items()
|
||||||
if re.match('%s.*,%s' %
|
if re.match('%s.*,%s' %
|
||||||
(re.escape(self.__prefix),
|
(re.escape(self.__prefix),
|
||||||
re.escape(self.dn(base))), k)]
|
re.escape(base)), k)]
|
||||||
results.extend(extraresults)
|
results.extend(extraresults)
|
||||||
elif scope == ldap.SCOPE_ONELEVEL:
|
elif scope == ldap.SCOPE_ONELEVEL:
|
||||||
|
|
||||||
def get_entries():
|
def get_entries():
|
||||||
base_dn = ldap.dn.str2dn(common.utf8_encode(base))
|
base_dn = ldap.dn.str2dn(base)
|
||||||
base_len = len(base_dn)
|
base_len = len(base_dn)
|
||||||
|
|
||||||
for k, v in self.db.items():
|
for k, v in self.db.items():
|
||||||
if not k.startswith(self.__prefix):
|
if not k.startswith(self.__prefix):
|
||||||
continue
|
continue
|
||||||
k_dn_str = k[len(self.__prefix):]
|
k_dn_str = k[len(self.__prefix):]
|
||||||
k_dn = ldap.dn.str2dn(common.utf8_encode(k_dn_str))
|
k_dn = ldap.dn.str2dn(k_dn_str)
|
||||||
if len(k_dn) != base_len + 1:
|
if len(k_dn) != base_len + 1:
|
||||||
continue
|
continue
|
||||||
if k_dn[-base_len:] != base_dn:
|
if k_dn[-base_len:] != base_dn:
|
||||||
|
@ -511,13 +503,11 @@ class FakeLdap(common.LDAPHandler):
|
||||||
objects = []
|
objects = []
|
||||||
for dn, attrs in results:
|
for dn, attrs in results:
|
||||||
# filter the objects by filterstr
|
# filter the objects by filterstr
|
||||||
id_attr, id_val, _ = ldap.dn.str2dn(common.utf8_encode(dn))[0][0]
|
id_attr, id_val, _ = ldap.dn.str2dn(dn)[0][0]
|
||||||
id_attr = common.utf8_decode(id_attr)
|
|
||||||
id_val = common.utf8_decode(id_val)
|
|
||||||
match_attrs = attrs.copy()
|
match_attrs = attrs.copy()
|
||||||
match_attrs[id_attr] = [id_val]
|
match_attrs[id_attr] = [id_val]
|
||||||
attrs_checked = set()
|
attrs_checked = set()
|
||||||
if not filterstr or _match_query(common.utf8_decode(filterstr),
|
if not filterstr or _match_query(filterstr,
|
||||||
match_attrs,
|
match_attrs,
|
||||||
attrs_checked):
|
attrs_checked):
|
||||||
if (filterstr and
|
if (filterstr and
|
||||||
|
@ -650,8 +640,7 @@ class FakeLdapNoSubtreeDelete(FakeLdap):
|
||||||
raise ldap.NOT_ALLOWED_ON_NONLEAF
|
raise ldap.NOT_ALLOWED_ON_NONLEAF
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug('delete item failed: dn=%s not found.',
|
LOG.debug('delete item failed: dn=%s not found.', dn)
|
||||||
common.utf8_decode(dn))
|
|
||||||
raise ldap.NO_SUCH_OBJECT
|
raise ldap.NO_SUCH_OBJECT
|
||||||
super(FakeLdapNoSubtreeDelete, self).delete_ext_s(dn,
|
super(FakeLdapNoSubtreeDelete, self).delete_ext_s(dn,
|
||||||
serverctrls,
|
serverctrls,
|
||||||
|
|
|
@ -231,15 +231,3 @@ class LDAPIdentity(LdapPoolCommonTestMixin,
|
||||||
config_files = super(LDAPIdentity, self).config_files()
|
config_files = super(LDAPIdentity, self).config_files()
|
||||||
config_files.append(unit.dirs.tests_conf('backend_ldap_pool.conf'))
|
config_files.append(unit.dirs.tests_conf('backend_ldap_pool.conf'))
|
||||||
return config_files
|
return config_files
|
||||||
|
|
||||||
@mock.patch.object(common_ldap, 'utf8_encode')
|
|
||||||
def test_utf8_encoded_is_used_in_pool(self, mocked_method):
|
|
||||||
def side_effect(arg):
|
|
||||||
return arg
|
|
||||||
mocked_method.side_effect = side_effect
|
|
||||||
# invalidate the cache to get utf8_encode function called.
|
|
||||||
PROVIDERS.identity_api.get_user.invalidate(PROVIDERS.identity_api,
|
|
||||||
self.user_foo['id'])
|
|
||||||
PROVIDERS.identity_api.get_user(self.user_foo['id'])
|
|
||||||
mocked_method.assert_any_call(CONF.ldap.user)
|
|
||||||
mocked_method.assert_any_call(CONF.ldap.password)
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import subprocess
|
||||||
|
|
||||||
import ldap.modlist
|
import ldap.modlist
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
|
from six import PY2
|
||||||
|
|
||||||
from keystone.common import provider_api
|
from keystone.common import provider_api
|
||||||
import keystone.conf
|
import keystone.conf
|
||||||
|
@ -30,7 +31,14 @@ PROVIDERS = provider_api.ProviderAPIs
|
||||||
|
|
||||||
|
|
||||||
def create_object(dn, attrs):
|
def create_object(dn, attrs):
|
||||||
conn = ldap.initialize(CONF.ldap.url)
|
if PY2:
|
||||||
|
# NOTE: Once https://github.com/python-ldap/python-ldap/issues/249
|
||||||
|
# is released, we can pass bytes_strictness='warn' as a parameter to
|
||||||
|
# ldap.initialize instead of setting it after ldap.initialize.
|
||||||
|
conn = ldap.initialize(CONF.ldap.url, bytes_mode=False)
|
||||||
|
conn.bytes_strictness = 'warn'
|
||||||
|
else:
|
||||||
|
conn = ldap.initialize(CONF.ldap.url)
|
||||||
conn.simple_bind_s(CONF.ldap.user, CONF.ldap.password)
|
conn.simple_bind_s(CONF.ldap.user, CONF.ldap.password)
|
||||||
ldif = ldap.modlist.addModlist(attrs)
|
ldif = ldap.modlist.addModlist(attrs)
|
||||||
conn.add_s(dn, ldif)
|
conn.add_s(dn, ldif)
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import ldap.modlist
|
import ldap.modlist
|
||||||
|
from six import PY2
|
||||||
|
|
||||||
from keystone.common import provider_api
|
from keystone.common import provider_api
|
||||||
import keystone.conf
|
import keystone.conf
|
||||||
|
@ -28,7 +29,14 @@ PROVIDERS = provider_api.ProviderAPIs
|
||||||
|
|
||||||
|
|
||||||
def create_object(dn, attrs):
|
def create_object(dn, attrs):
|
||||||
conn = ldap.initialize(CONF.ldap.url)
|
if PY2:
|
||||||
|
# NOTE: Once https://github.com/python-ldap/python-ldap/issues/249
|
||||||
|
# is released, we can pass bytes_strictness='warn' as a parameter to
|
||||||
|
# ldap.initialize instead of setting it after ldap.initialize.
|
||||||
|
conn = ldap.initialize(CONF.ldap.url, bytes_mode=False)
|
||||||
|
conn.bytes_strictness = 'warn'
|
||||||
|
else:
|
||||||
|
conn = ldap.initialize(CONF.ldap.url)
|
||||||
conn.simple_bind_s(CONF.ldap.user, CONF.ldap.password)
|
conn.simple_bind_s(CONF.ldap.user, CONF.ldap.password)
|
||||||
ldif = ldap.modlist.addModlist(attrs)
|
ldif = ldap.modlist.addModlist(attrs)
|
||||||
conn.add_s(dn, ldif)
|
conn.add_s(dn, ldif)
|
||||||
|
|
|
@ -17,7 +17,7 @@ iso8601==0.1.12
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
keystoneauth1==3.4.0
|
keystoneauth1==3.4.0
|
||||||
keystonemiddleware==5.1.0
|
keystonemiddleware==5.1.0
|
||||||
ldappool===2.0.0
|
ldappool===2.3.1
|
||||||
lxml==3.4.1
|
lxml==3.4.1
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
msgpack==0.5.0
|
msgpack==0.5.0
|
||||||
|
|
Loading…
Reference in New Issue