diff --git a/keystone/resource/core.py b/keystone/resource/core.py index 0c2397a2f7..02fea5262c 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -37,7 +37,7 @@ MEMOIZE = cache.get_memoization_decorator(group='resource') @dependency.provider('resource_api') @dependency.requires('assignment_api', 'credential_api', 'domain_config_api', - 'identity_api', 'revoke_api') + 'identity_api', 'revoke_api', 'trust_api') class Manager(manager.Manager): """Default pivot point for the Resource backend. @@ -467,6 +467,7 @@ class Manager(manager.Manager): # the specified project assignment.COMPUTED_ASSIGNMENTS_REGION.invalidate() self.credential_api.delete_credentials_for_project(project_id) + self.trust_api.delete_trusts_for_project(project_id) finally: # attempt to send audit event even if the cache invalidation raises notifications.Audit.deleted(self._PROJECT, project_id, initiator) diff --git a/keystone/tests/unit/test_v3_trust.py b/keystone/tests/unit/test_v3_trust.py index 77a663c13d..02b259db72 100644 --- a/keystone/tests/unit/test_v3_trust.py +++ b/keystone/tests/unit/test_v3_trust.py @@ -508,3 +508,30 @@ class TestTrustOperations(test_v3.RestfulTestCase): self.assertRaises(exception.TrustNotFound, self.trust_api.get_trust, trust['id']) + + def test_trust_deleted_when_project_deleted(self): + # create trust + ref = unit.new_trust_ref( + trustor_user_id=self.user_id, + trustee_user_id=self.trustee_user_id, + project_id=self.project_id, + impersonation=False, + role_ids=[self.role_id], + allow_redelegation=True) + resp = self.post('/OS-TRUST/trusts', body={'trust': ref}) + + trust = self.assertValidTrustResponse(resp) + + # list all trusts + r = self.get('/OS-TRUST/trusts') + self.assertEqual(1, len(r.result['trusts'])) + + # delete the project will delete the trust. + self.delete( + '/projects/%(project_id)s' % {'project_id': trust['project_id']}) + + # call the backend method directly to bypass authentication since the + # user no longer has the assignment on the project. + self.assertRaises(exception.TrustNotFound, + self.trust_api.get_trust, + trust['id']) diff --git a/keystone/trust/backends/base.py b/keystone/trust/backends/base.py index 5ebfab1904..4e11979253 100644 --- a/keystone/trust/backends/base.py +++ b/keystone/trust/backends/base.py @@ -70,3 +70,12 @@ class TrustDriverBase(object): :raises keystone.exception.TrustNotFound: If the trust doesn't exist. """ raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_trusts_for_project(self, project_id): + """Delete all trusts for a project. + + :param project_id: ID of a project to filter trusts by. + + """ + raise exception.NotImplemented() # pragma: no cover diff --git a/keystone/trust/backends/sql.py b/keystone/trust/backends/sql.py index 9cdbd076d3..378fa57769 100644 --- a/keystone/trust/backends/sql.py +++ b/keystone/trust/backends/sql.py @@ -176,3 +176,10 @@ class Trust(base.TrustDriverBase): if not trust_ref: raise exception.TrustNotFound(trust_id=trust_id) trust_ref.deleted_at = timeutils.utcnow() + + def delete_trusts_for_project(self, project_id): + with sql.session_for_write() as session: + query = session.query(TrustModel) + trusts = query.filter_by(project_id=project_id) + for trust_ref in trusts: + trust_ref.deleted_at = timeutils.utcnow() diff --git a/releasenotes/notes/bug-1622310-c501cf77437fdfa6.yaml b/releasenotes/notes/bug-1622310-c501cf77437fdfa6.yaml new file mode 100644 index 0000000000..c5318a33a5 --- /dev/null +++ b/releasenotes/notes/bug-1622310-c501cf77437fdfa6.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - > + [`bug 1622310 `_] + Keystone trust will be invalidated if the project to which the trust + is scoped, or the user (trustor or trustee) for which the delegation + is assigned, have been deleted. +other: + - Abstract method ``delete_trusts_for_project`` should be implemented by + custom drivers.