aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/common/ldap/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/common/ldap/core.py')
-rw-r--r--keystone-moon/keystone/common/ldap/core.py137
1 files changed, 78 insertions, 59 deletions
diff --git a/keystone-moon/keystone/common/ldap/core.py b/keystone-moon/keystone/common/ldap/core.py
index 6386ae2a..d94aa04c 100644
--- a/keystone-moon/keystone/common/ldap/core.py
+++ b/keystone-moon/keystone/common/ldap/core.py
@@ -20,12 +20,15 @@ import re
import sys
import weakref
+import ldap.controls
import ldap.filter
import ldappool
from oslo_log import log
+from oslo_utils import reflection
import six
from six.moves import map, zip
+from keystone.common import driver_hints
from keystone import exception
from keystone.i18n import _
from keystone.i18n import _LW
@@ -62,15 +65,17 @@ def utf8_encode(value):
:param value: A basestring
:returns: UTF-8 encoded version of value
- :raises: TypeError if value is not basestring
+ :raises TypeError: If value is not basestring
"""
if isinstance(value, six.text_type):
return _utf8_encoder(value)[0]
elif isinstance(value, six.binary_type):
return value
else:
+ value_cls_name = reflection.get_class_name(
+ value, fully_qualified=False)
raise TypeError("value must be basestring, "
- "not %s" % value.__class__.__name__)
+ "not %s" % value_cls_name)
_utf8_decoder = codecs.getdecoder('utf-8')
@@ -84,7 +89,7 @@ def utf8_decode(value):
:param value: value to be returned as unicode
:returns: value as unicode
- :raises: UnicodeDecodeError for invalid UTF-8 encoding
+ :raises UnicodeDecodeError: for invalid UTF-8 encoding
"""
if isinstance(value, six.binary_type):
return _utf8_decoder(value)[0]
@@ -110,14 +115,15 @@ def py2ldap(val):
def enabled2py(val):
"""Similar to ldap2py, only useful for the enabled attribute."""
-
try:
return LDAP_VALUES[val]
- except KeyError:
+ except KeyError: # nosec
+ # It wasn't a boolean value, will try as an int instead.
pass
try:
return int(val)
- except ValueError:
+ except ValueError: # nosec
+ # It wasn't an int either, will try as utf8 instead.
pass
return utf8_decode(val)
@@ -239,7 +245,6 @@ def is_ava_value_equal(attribute_type, val1, val2):
that function apply here.
"""
-
return prep_case_insensitive(val1) == prep_case_insensitive(val2)
@@ -259,7 +264,6 @@ def is_rdn_equal(rdn1, rdn2):
limitations of that function apply here.
"""
-
if len(rdn1) != len(rdn2):
return False
@@ -292,7 +296,6 @@ def is_dn_equal(dn1, dn2):
:param dn2: Either a string DN or a DN parsed by ldap.dn.str2dn.
"""
-
if not isinstance(dn1, list):
dn1 = ldap.dn.str2dn(utf8_encode(dn1))
if not isinstance(dn2, list):
@@ -314,7 +317,6 @@ def dn_startswith(descendant_dn, dn):
:param dn: Either a string DN or a DN parsed by ldap.dn.str2dn.
"""
-
if not isinstance(descendant_dn, list):
descendant_dn = ldap.dn.str2dn(utf8_encode(descendant_dn))
if not isinstance(dn, list):
@@ -419,6 +421,7 @@ class LDAPHandler(object):
derived classes.
"""
+
@abc.abstractmethod
def __init__(self, conn=None):
self.conn = conn
@@ -625,6 +628,7 @@ def _common_ldap_initialization(url, use_tls=False, tls_cacertfile=None,
class MsgId(list):
"""Wrapper class to hold connection and msgid."""
+
pass
@@ -665,6 +669,7 @@ class PooledLDAPHandler(LDAPHandler):
the methods in this class.
"""
+
# Added here to allow override for testing
Connector = ldappool.StateConnector
auth_pool_prefix = 'auth_pool_'
@@ -815,7 +820,6 @@ class PooledLDAPHandler(LDAPHandler):
which requested msgId and used it in result3 exits.
"""
-
conn, msg_id = msgid
return conn.result3(msg_id, all, timeout)
@@ -957,7 +961,7 @@ class KeystoneLDAPHandler(LDAPHandler):
if attrlist is not None:
attrlist = [attr for attr in attrlist if attr is not None]
LOG.debug('LDAP search_ext: base=%s scope=%s filterstr=%s '
- 'attrs=%s attrsonly=%s'
+ 'attrs=%s attrsonly=%s '
'serverctrls=%s clientctrls=%s timeout=%s sizelimit=%s',
base, scope, filterstr, attrlist, attrsonly,
serverctrls, clientctrls, timeout, sizelimit)
@@ -1041,7 +1045,11 @@ class KeystoneLDAPHandler(LDAPHandler):
'resp_ctrl_classes=%s ldap_result=%s',
msgid, all, timeout, resp_ctrl_classes, ldap_result)
- py_result = convert_ldap_result(ldap_result)
+ # ldap_result returned from result3 is a tuple of
+ # (rtype, rdata, rmsgid, serverctrls). We don't need use of these,
+ # except rdata.
+ rtype, rdata, rmsgid, serverctrls = ldap_result
+ py_result = convert_ldap_result(rdata)
return py_result
def modify_s(self, dn, modlist):
@@ -1221,7 +1229,7 @@ class BaseLdap(object):
try:
ldap_attr, attr_map = item.split(':')
except Exception:
- LOG.warn(_LW(
+ LOG.warning(_LW(
'Invalid additional attribute mapping: "%s". '
'Format must be <ldap_attribute>:<keystone_attribute>'),
item)
@@ -1337,7 +1345,7 @@ class BaseLdap(object):
'as an ID. Will get the ID from DN instead') % (
{'id_attr': self.id_attr,
'dn': res[0]})
- LOG.warn(message)
+ LOG.warning(message)
id_val = self._dn_to_id(res[0])
else:
id_val = id_attrs[0]
@@ -1354,7 +1362,8 @@ class BaseLdap(object):
continue
v = lower_res[map_attr.lower()]
- except KeyError:
+ except KeyError: # nosec
+ # Didn't find the attr, so don't add it.
pass
else:
try:
@@ -1383,7 +1392,8 @@ class BaseLdap(object):
if values.get('name') is not None:
try:
self.get_by_name(values['name'])
- except exception.NotFound:
+ except exception.NotFound: # nosec
+ # Didn't find it so it's unique, good.
pass
else:
raise exception.Conflict(type=self.options_name,
@@ -1393,7 +1403,8 @@ class BaseLdap(object):
if values.get('id') is not None:
try:
self.get(values['id'])
- except exception.NotFound:
+ except exception.NotFound: # nosec
+ # Didn't find it, so it's unique, good.
pass
else:
raise exception.Conflict(type=self.options_name,
@@ -1452,16 +1463,39 @@ class BaseLdap(object):
except IndexError:
return None
- def _ldap_get_all(self, ldap_filter=None):
+ def _ldap_get_limited(self, base, scope, filterstr, attrlist, sizelimit):
+ with self.get_connection() as conn:
+ try:
+ control = ldap.controls.libldap.SimplePagedResultsControl(
+ criticality=True,
+ size=sizelimit,
+ cookie='')
+ msgid = conn.search_ext(base, scope, filterstr, attrlist,
+ serverctrls=[control])
+ rdata = conn.result3(msgid)
+ return rdata
+ except ldap.NO_SUCH_OBJECT:
+ return []
+
+ @driver_hints.truncated
+ def _ldap_get_all(self, hints, ldap_filter=None):
query = u'(&%s(objectClass=%s)(%s=*))' % (
ldap_filter or self.ldap_filter or '',
self.object_class,
self.id_attr)
+ sizelimit = 0
+ attrs = list(set(([self.id_attr] +
+ list(self.attribute_mapping.values()) +
+ list(self.extra_attr_mapping.keys()))))
+ if hints.limit:
+ sizelimit = hints.limit['limit']
+ return self._ldap_get_limited(self.tree_dn,
+ self.LDAP_SCOPE,
+ query,
+ attrs,
+ sizelimit)
with self.get_connection() as conn:
try:
- attrs = list(set(([self.id_attr] +
- list(self.attribute_mapping.values()) +
- list(self.extra_attr_mapping.keys()))))
return conn.search_s(self.tree_dn,
self.LDAP_SCOPE,
query,
@@ -1501,9 +1535,10 @@ class BaseLdap(object):
except IndexError:
raise self._not_found(name)
- def get_all(self, ldap_filter=None):
+ def get_all(self, ldap_filter=None, hints=None):
+ hints = hints or driver_hints.Hints()
return [self._ldap_res_to_model(x)
- for x in self._ldap_get_all(ldap_filter)]
+ for x in self._ldap_get_all(hints, ldap_filter)]
def update(self, object_id, values, old_obj=None):
if old_obj is None:
@@ -1565,7 +1600,7 @@ class BaseLdap(object):
except ldap.NO_SUCH_OBJECT:
raise self._not_found(object_id)
- def deleteTree(self, object_id):
+ def delete_tree(self, object_id):
tree_delete_control = ldap.controls.LDAPControl(CONTROL_TREEDELETE,
0,
None)
@@ -1609,8 +1644,8 @@ class BaseLdap(object):
:param member_list_dn: DN of group to which the
member will be added.
- :raises: exception.Conflict: If the user was already a member.
- self.NotFound: If the group entry didn't exist.
+ :raises keystone.exception.Conflict: If the user was already a member.
+ :raises self.NotFound: If the group entry didn't exist.
"""
with self.get_connection() as conn:
try:
@@ -1632,8 +1667,8 @@ class BaseLdap(object):
:param member_list_dn: DN of group from which the
member will be removed.
- :raises: self.NotFound: If the group entry didn't exist.
- ldap.NO_SUCH_ATTRIBUTE: If the user wasn't a member.
+ :raises self.NotFound: If the group entry didn't exist.
+ :raises ldap.NO_SUCH_ATTRIBUTE: If the user wasn't a member.
"""
with self.get_connection() as conn:
try:
@@ -1666,11 +1701,12 @@ class BaseLdap(object):
not_deleted_nodes.append(node_dn)
if not_deleted_nodes:
- LOG.warn(_LW("When deleting entries for %(search_base)s, could not"
- " delete nonexistent entries %(entries)s%(dots)s"),
- {'search_base': search_base,
- 'entries': not_deleted_nodes[:3],
- 'dots': '...' if len(not_deleted_nodes) > 3 else ''})
+ LOG.warning(_LW("When deleting entries for %(search_base)s, "
+ "could not delete nonexistent entries "
+ "%(entries)s%(dots)s"),
+ {'search_base': search_base,
+ 'entries': not_deleted_nodes[:3],
+ 'dots': '...' if len(not_deleted_nodes) > 3 else ''})
def filter_query(self, hints, query=None):
"""Applies filtering to a query.
@@ -1823,7 +1859,8 @@ class EnabledEmuMixIn(BaseLdap):
def _get_enabled(self, object_id, conn):
dn = self._id_to_dn(object_id)
- query = '(%s=%s)' % (self.member_attribute, dn)
+ query = '(%s=%s)' % (self.member_attribute,
+ ldap.filter.escape_filter_chars(dn))
try:
enabled_value = conn.search_s(self.enabled_emulation_dn,
ldap.SCOPE_BASE,
@@ -1857,7 +1894,8 @@ class EnabledEmuMixIn(BaseLdap):
with self.get_connection() as conn:
try:
conn.modify_s(self.enabled_emulation_dn, modlist)
- except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE):
+ except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE): # nosec
+ # It's already gone, good.
pass
def create(self, values):
@@ -1880,11 +1918,12 @@ class EnabledEmuMixIn(BaseLdap):
ref['enabled'] = self._get_enabled(object_id, conn)
return ref
- def get_all(self, ldap_filter=None):
+ def get_all(self, ldap_filter=None, hints=None):
+ hints = hints or driver_hints.Hints()
if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
# had to copy BaseLdap.get_all here to ldap_filter by DN
tenant_list = [self._ldap_res_to_model(x)
- for x in self._ldap_get_all(ldap_filter)
+ for x in self._ldap_get_all(hints, ldap_filter)
if x[0] != self.enabled_emulation_dn]
with self.get_connection() as conn:
for tenant_ref in tenant_list:
@@ -1892,7 +1931,7 @@ class EnabledEmuMixIn(BaseLdap):
tenant_ref['id'], conn)
return tenant_list
else:
- return super(EnabledEmuMixIn, self).get_all(ldap_filter)
+ return super(EnabledEmuMixIn, self).get_all(ldap_filter, hints)
def update(self, object_id, values, old_obj=None):
if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
@@ -1914,23 +1953,3 @@ class EnabledEmuMixIn(BaseLdap):
if self.enabled_emulation:
self._remove_enabled(object_id)
super(EnabledEmuMixIn, self).delete(object_id)
-
-
-class ProjectLdapStructureMixin(object):
- """Project LDAP Structure shared between LDAP backends.
-
- This is shared between the resource and assignment LDAP backends.
-
- """
- DEFAULT_OU = 'ou=Groups'
- DEFAULT_STRUCTURAL_CLASSES = []
- DEFAULT_OBJECTCLASS = 'groupOfNames'
- DEFAULT_ID_ATTR = 'cn'
- NotFound = exception.ProjectNotFound
- notfound_arg = 'project_id' # NOTE(yorik-sar): while options_name = tenant
- options_name = 'project'
- attribute_options_names = {'name': 'name',
- 'description': 'desc',
- 'enabled': 'enabled',
- 'domain_id': 'domain_id'}
- immutable_attrs = ['name']