diff --git a/keystone/assignment/backends/ldap.py b/keystone/assignment/backends/ldap.py index 6852a4f97d..d3550d0bb2 100644 --- a/keystone/assignment/backends/ldap.py +++ b/keystone/assignment/backends/ldap.py @@ -629,16 +629,7 @@ class RoleApi(common_ldap.BaseLdap): return res def roles_delete_subtree_by_project(self, tenant_dn): - conn = self.get_connection() - query = '(objectClass=%s)' % self.object_class - try: - roles = conn.search_s(tenant_dn, ldap.SCOPE_ONELEVEL, query) - for role_dn, _ in roles: - conn.delete_s(role_dn) - except ldap.NO_SUCH_OBJECT: - pass - finally: - conn.unbind_s() + self._delete_tree_nodes(tenant_dn, ldap.SCOPE_ONELEVEL) def update(self, role_id, role): try: @@ -649,24 +640,8 @@ class RoleApi(common_ldap.BaseLdap): return super(RoleApi, self).update(role_id, role) def delete(self, role_id, tenant_dn): - conn = self.get_connection() - role_id_esc = ldap.filter.escape_filter_chars(role_id) - query = '(&(objectClass=%s)(%s=%s))' % (self.object_class, - self.id_attr, role_id_esc) - try: - # RFC 4511 (The LDAP Protocol) defines a list containing only the - # OID "1.1" as indicating that no attributes should be returned. - # The following code only needs the DN of the entries. - request_no_attributes = ['1.1'] - for role_dn, _ in conn.search_s(tenant_dn, - ldap.SCOPE_SUBTREE, - query, - attrlist=request_no_attributes): - conn.delete_s(role_dn) - except ldap.NO_SUCH_OBJECT: - pass - finally: - conn.unbind_s() + self._delete_tree_nodes(tenant_dn, ldap.SCOPE_SUBTREE, query_params={ + self.id_attr: role_id}) super(RoleApi, self).delete(role_id) def list_role_assignments(self, project_tree_dn): diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py index e3fb1fc84f..ae358a6ccc 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -1215,6 +1215,42 @@ class BaseLdap(object): finally: conn.unbind_s() + def _delete_tree_nodes(self, search_base, scope, query_params=None): + conn = self.get_connection() + query = u'(objectClass=%s)' % self.object_class + if query_params: + query = (u'(&%s%s)' % + (query, ''.join(['(%s=%s)' + % (k, ldap.filter.escape_filter_chars(v)) + for k, v in + six.iteritems(query_params)]))) + not_deleted_nodes = [] + try: + # RFC 4511 (The LDAP Protocol) defines a list containing only the + # OID "1.1" as indicating that no attributes should be returned. + # The following code only needs the DN of the entries. + request_no_attributes = ['1.1'] + nodes = conn.search_s(search_base, scope, query, + attrlist=request_no_attributes) + except ldap.NO_SUCH_OBJECT: + LOG.debug('Could not find entry with dn=%s', search_base) + raise self._not_found(self._dn_to_id(search_base)) + else: + for node_dn, _t in nodes: + try: + conn.delete_s(node_dn) + except ldap.NO_SUCH_OBJECT: + not_deleted_nodes.append(node_dn) + finally: + conn.unbind_s() + + if not_deleted_nodes: + LOG.warn(_("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 ''}) + class EnabledEmuMixIn(BaseLdap): """Emulates boolean 'enabled' attribute if turned on. diff --git a/keystone/identity/backends/ldap.py b/keystone/identity/backends/ldap.py index 1cd34a903b..449c76e127 100644 --- a/keystone/identity/backends/ldap.py +++ b/keystone/identity/backends/ldap.py @@ -284,20 +284,10 @@ class GroupApi(common_ldap.BaseLdap): # TODO(spzala): this is only placeholder for group and domain # role support which will be added under bug 1101287 - query = '(objectClass=%s)' % self.object_class group_ref = self.get(group_id) group_dn = group_ref['dn'] if group_dn: - try: - conn = self.get_connection() - roles = conn.search_s(group_dn, ldap.SCOPE_ONELEVEL, - query, ['%s' % '1.1']) - for role_dn, _ in roles: - conn.delete_s(role_dn) - except ldap.NO_SUCH_OBJECT: - pass - finally: - conn.unbind_s() + self._delete_tree_nodes(group_dn, ldap.SCOPE_ONELEVEL) super(GroupApi, self).delete(group_id) def update(self, group_id, values):