diff options
-rw-r--r-- | etc/keystone.conf.sample | 1 | ||||
-rw-r--r-- | keystone/common/ldap/core.py | 15 | ||||
-rw-r--r-- | keystone/common/ldap/fakeldap.py | 14 | ||||
-rw-r--r-- | keystone/config.py | 1 | ||||
-rw-r--r-- | keystone/identity/backends/ldap/core.py | 20 | ||||
-rw-r--r-- | tests/test_backend.py | 10 |
6 files changed, 60 insertions, 1 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 1d486768..0097b6c1 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -107,6 +107,7 @@ # password = None # suffix = cn=example,cn=com # use_dumb_member = False +# allow_subtree_delete = False # user_tree_dn = ou=Users,dc=example,dc=com # user_objectclass = inetOrgPerson diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py index b077ad97..5169cd86 100644 --- a/keystone/common/ldap/core.py +++ b/keystone/common/ldap/core.py @@ -25,6 +25,7 @@ LOG = logging.getLogger(__name__) LDAP_VALUES = {'TRUE': True, 'FALSE': False} +CONTROL_TREEDELETE = '1.2.840.113556.1.4.805' def py2ldap(val): @@ -94,6 +95,8 @@ class BaseLdap(object): self.structural_classes = self.DEFAULT_STRUCTURAL_CLASSES self.use_dumb_member = getattr(conf.ldap, 'use_dumb_member') or True + self.subtree_delete_enabled = getattr(conf.ldap, + 'allow_subtree_delete') def get_connection(self, user=None, password=None): if self.LDAP_URL.startswith('fake://'): @@ -288,6 +291,14 @@ class BaseLdap(object): conn = self.get_connection() conn.delete_s(self._id_to_dn(id)) + def deleteTree(self, id): + conn = self.get_connection() + tree_delete_control = ldap.controls.LDAPControl(CONTROL_TREEDELETE, + 0, + None) + conn.delete_ext_s(self._id_to_dn(id), + serverctrls=[tree_delete_control]) + class LdapWrapper(object): def __init__(self, url): @@ -341,3 +352,7 @@ class LdapWrapper(object): def delete_s(self, dn): LOG.debug("LDAP delete: dn=%s", dn) return self.conn.delete_s(dn) + + def delete_ext_s(self, dn, serverctrls): + LOG.debug("LDAP delete_ext: dn=%s, serverctrls=%s", dn, serverctrls) + return self.conn.delete_ext_s(dn, serverctrls) diff --git a/keystone/common/ldap/fakeldap.py b/keystone/common/ldap/fakeldap.py index 77d2bfe4..bfbefd78 100644 --- a/keystone/common/ldap/fakeldap.py +++ b/keystone/common/ldap/fakeldap.py @@ -212,6 +212,20 @@ class FakeLdap(object): raise ldap.NO_SUCH_OBJECT self.db.sync() + def delete_ext_s(self, dn, serverctrls): + """Remove the ldap object at specified dn.""" + if server_fail: + raise ldap.SERVER_DOWN + + key = '%s%s' % (self.__prefix, dn) + LOG.debug('FakeLdap delete item: dn=%s', dn) + try: + del self.db[key] + except KeyError: + LOG.error('FakeLdap delete item failed: dn=%s not found.', dn) + raise ldap.NO_SUCH_OBJECT + self.db.sync() + def modify_s(self, dn, attrs): """Modify the object at dn using the attribute list. diff --git a/keystone/config.py b/keystone/config.py index 5fed916c..a2586e9d 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -163,6 +163,7 @@ register_str('password', group='ldap', default='freeipa4all') register_str('suffix', group='ldap', default='cn=example,cn=com') register_bool('use_dumb_member', group='ldap', default=False) register_str('user_name_attribute', group='ldap', default='sn') +register_bool('allow_subtree_delete', group='ldap', default=False) register_str('user_tree_dn', group='ldap', default=None) diff --git a/keystone/identity/backends/ldap/core.py b/keystone/identity/backends/ldap/core.py index 07ff506e..f0a004de 100644 --- a/keystone/identity/backends/ldap/core.py +++ b/keystone/identity/backends/ldap/core.py @@ -572,7 +572,11 @@ class TenantApi(common_ldap.BaseLdap, ApiShimMixin): return list(res) def delete(self, id): - super(TenantApi, self).delete(id) + if self.subtree_delete_enabled: + super(TenantApi, self).deleteTree(id) + else: + self.role_api.roles_delete_subtree_by_tenant(id) + super(TenantApi, self).delete(id) def update(self, id, values): try: @@ -894,6 +898,20 @@ class RoleApi(common_ldap.BaseLdap, ApiShimMixin): tenant_id=tenant_id)) return res + def roles_delete_subtree_by_tenant(self, tenant_id): + conn = self.get_connection() + query = '(objectClass=%s)' % self.object_class + tenant_dn = self.tenant_api._id_to_dn(tenant_id) + try: + roles = conn.search_s(tenant_dn, ldap.SCOPE_ONELEVEL, query) + for role_dn, _ in roles: + try: + conn.delete_s(role_dn) + except Exception as inst: + raise inst + except ldap.NO_SUCH_OBJECT: + pass + def rolegrant_get_by_ids(self, user_id, role_id, tenant_id): conn = self.get_connection() user_dn = self.user_api._id_to_dn(user_id) diff --git a/tests/test_backend.py b/tests/test_backend.py index 6865b4fc..d65eb181 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -624,6 +624,16 @@ class IdentityTests(object): for test_tenant in default_fixtures.TENANTS: self.assertTrue(x for x in tenants if x['id'] == test_tenant['id']) + def test_delete_tenant_with_role_assignments(self): + tenant = {'id': 'fake1', 'name': 'fake1'} + self.identity_api.create_tenant('fake1', tenant) + self.identity_api.add_role_to_user_and_tenant( + self.user_foo['id'], tenant['id'], 'useless') + self.identity_api.delete_tenant(tenant['id']) + self.assertRaises(exception.NotFound, + self.identity_api.get_tenant, + tenant['id']) + class TokenTests(object): def test_token_crud(self): |