Merge "Revoke user tokens when disabling/delete a project"
This commit is contained in:
		@@ -169,6 +169,10 @@ class V2Controller(wsgi.Application):
 | 
			
		||||
            self._delete_tokens_for_trust(trust['trustee_user_id'],
 | 
			
		||||
                                          trust['id'])
 | 
			
		||||
 | 
			
		||||
    def _delete_tokens_for_project(self, project_id):
 | 
			
		||||
        for user_ref in self.identity_api.get_project_users(project_id):
 | 
			
		||||
            self._delete_tokens_for_user(user_ref['id'], project_id=project_id)
 | 
			
		||||
 | 
			
		||||
    def _require_attribute(self, ref, attr):
 | 
			
		||||
        """Ensures the reference contains the specified attribute."""
 | 
			
		||||
        if ref.get(attr) is None or ref.get(attr) == '':
 | 
			
		||||
 
 | 
			
		||||
@@ -109,12 +109,20 @@ class Tenant(controller.V2Controller):
 | 
			
		||||
        # be specifying that
 | 
			
		||||
        clean_tenant = tenant.copy()
 | 
			
		||||
        clean_tenant.pop('domain_id', None)
 | 
			
		||||
 | 
			
		||||
        # If the project has been disabled (or enabled=False) we are
 | 
			
		||||
        # deleting the tokens for that project.
 | 
			
		||||
        if not tenant.get('enabled', True):
 | 
			
		||||
            self._delete_tokens_for_project(tenant_id)
 | 
			
		||||
 | 
			
		||||
        tenant_ref = self.identity_api.update_project(
 | 
			
		||||
            tenant_id, clean_tenant)
 | 
			
		||||
        return {'tenant': tenant_ref}
 | 
			
		||||
 | 
			
		||||
    def delete_project(self, context, tenant_id):
 | 
			
		||||
        self.assert_admin(context)
 | 
			
		||||
        # Delete all tokens belonging to the users for that project
 | 
			
		||||
        self._delete_tokens_for_project(tenant_id)
 | 
			
		||||
        self.identity_api.delete_project(tenant_id)
 | 
			
		||||
 | 
			
		||||
    def get_project_users(self, context, tenant_id, **kw):
 | 
			
		||||
@@ -572,6 +580,10 @@ class ProjectV3(controller.V3Controller):
 | 
			
		||||
    def update_project(self, context, project_id, project):
 | 
			
		||||
        self._require_matching_id(project_id, project)
 | 
			
		||||
 | 
			
		||||
        # The project was disabled so we delete the tokens
 | 
			
		||||
        if not project.get('enabled', True):
 | 
			
		||||
            self._delete_tokens_for_project(project_id)
 | 
			
		||||
 | 
			
		||||
        ref = self.identity_api.update_project(project_id, project)
 | 
			
		||||
        return ProjectV3.wrap_member(context, ref)
 | 
			
		||||
 | 
			
		||||
@@ -580,6 +592,10 @@ class ProjectV3(controller.V3Controller):
 | 
			
		||||
        for cred in self.credential_api.list_credentials():
 | 
			
		||||
            if cred['project_id'] == project_id:
 | 
			
		||||
                self.credential_api.delete_credential(cred['id'])
 | 
			
		||||
 | 
			
		||||
        # Delete all tokens belonging to the users for that project
 | 
			
		||||
        self._delete_tokens_for_project(project_id)
 | 
			
		||||
 | 
			
		||||
        # Finally delete the project itself - the backend is
 | 
			
		||||
        # responsible for deleting any role assignments related
 | 
			
		||||
        # to this project
 | 
			
		||||
 
 | 
			
		||||
@@ -378,6 +378,46 @@ class KeystoneClientTests(object):
 | 
			
		||||
                          client.tokens.authenticate,
 | 
			
		||||
                          token=token_id)
 | 
			
		||||
 | 
			
		||||
    def test_disable_tenant_invalidates_token(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        admin_client = self.get_client(admin=True)
 | 
			
		||||
        foo_client = self.get_client(self.user_foo)
 | 
			
		||||
        tenant_bar = admin_client.tenants.get(self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        # Disable the tenant.
 | 
			
		||||
        tenant_bar.update(enabled=False)
 | 
			
		||||
 | 
			
		||||
        # Test that the token has been removed.
 | 
			
		||||
        self.assertRaises(client_exceptions.Unauthorized,
 | 
			
		||||
                          foo_client.tokens.authenticate,
 | 
			
		||||
                          token=foo_client.auth_token)
 | 
			
		||||
 | 
			
		||||
        # Test that the user access has been disabled.
 | 
			
		||||
        self.assertRaises(client_exceptions.Unauthorized,
 | 
			
		||||
                          self.get_client,
 | 
			
		||||
                          self.user_foo)
 | 
			
		||||
 | 
			
		||||
    def test_delete_tenant_invalidates_token(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        admin_client = self.get_client(admin=True)
 | 
			
		||||
        foo_client = self.get_client(self.user_foo)
 | 
			
		||||
        tenant_bar = admin_client.tenants.get(self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        # Delete the tenant.
 | 
			
		||||
        tenant_bar.delete()
 | 
			
		||||
 | 
			
		||||
        # Test that the token has been removed.
 | 
			
		||||
        self.assertRaises(client_exceptions.Unauthorized,
 | 
			
		||||
                          foo_client.tokens.authenticate,
 | 
			
		||||
                          token=foo_client.auth_token)
 | 
			
		||||
 | 
			
		||||
        # Test that the user access has been disabled.
 | 
			
		||||
        self.assertRaises(client_exceptions.Unauthorized,
 | 
			
		||||
                          self.get_client,
 | 
			
		||||
                          self.user_foo)
 | 
			
		||||
 | 
			
		||||
    def test_disable_user_invalidates_token(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
@@ -1165,6 +1205,12 @@ class KcEssex3TestCase(CompatTestCase, KeystoneClientTests):
 | 
			
		||||
    def test_policy_crud(self):
 | 
			
		||||
        self.skipTest('N/A due to lack of endpoint CRUD')
 | 
			
		||||
 | 
			
		||||
    def test_disable_tenant_invalidates_token(self):
 | 
			
		||||
        self.skipTest('N/A')
 | 
			
		||||
 | 
			
		||||
    def test_delete_tenant_invalidates_token(self):
 | 
			
		||||
        self.skipTest('N/A')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Kc11TestCase(CompatTestCase, KeystoneClientTests):
 | 
			
		||||
    def get_checkout(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -545,6 +545,67 @@ class TestTokenRevoking(test_v3.RestfulTestCase):
 | 
			
		||||
                  headers={'X-Subject-Token': token},
 | 
			
		||||
                  expected_status=204)
 | 
			
		||||
 | 
			
		||||
    def test_disabling_project_revokes_token(self):
 | 
			
		||||
        resp = self.post(
 | 
			
		||||
            '/auth/tokens',
 | 
			
		||||
            body=self.build_authentication_request(
 | 
			
		||||
                user_id=self.user3['id'],
 | 
			
		||||
                password=self.user3['password'],
 | 
			
		||||
                project_id=self.projectA['id']))
 | 
			
		||||
        token = resp.headers.get('X-Subject-Token')
 | 
			
		||||
 | 
			
		||||
        # confirm token is valid
 | 
			
		||||
        self.head('/auth/tokens',
 | 
			
		||||
                  headers={'X-Subject-Token': token},
 | 
			
		||||
                  expected_status=204)
 | 
			
		||||
 | 
			
		||||
        # disable the project, which should invalidate the token
 | 
			
		||||
        self.patch(
 | 
			
		||||
            '/projects/%(project_id)s' % {'project_id': self.projectA['id']},
 | 
			
		||||
            body={'project': {'enabled': False}})
 | 
			
		||||
 | 
			
		||||
        # user should no longer have access to the project
 | 
			
		||||
        self.head('/auth/tokens',
 | 
			
		||||
                  headers={'X-Subject-Token': token},
 | 
			
		||||
                  expected_status=401)
 | 
			
		||||
        resp = self.post(
 | 
			
		||||
            '/auth/tokens',
 | 
			
		||||
            body=self.build_authentication_request(
 | 
			
		||||
                user_id=self.user3['id'],
 | 
			
		||||
                password=self.user3['password'],
 | 
			
		||||
                project_id=self.projectA['id']),
 | 
			
		||||
            expected_status=401)
 | 
			
		||||
 | 
			
		||||
    def test_deleting_project_revokes_token(self):
 | 
			
		||||
        resp = self.post(
 | 
			
		||||
            '/auth/tokens',
 | 
			
		||||
            body=self.build_authentication_request(
 | 
			
		||||
                user_id=self.user3['id'],
 | 
			
		||||
                password=self.user3['password'],
 | 
			
		||||
                project_id=self.projectA['id']))
 | 
			
		||||
        token = resp.headers.get('X-Subject-Token')
 | 
			
		||||
 | 
			
		||||
        # confirm token is valid
 | 
			
		||||
        self.head('/auth/tokens',
 | 
			
		||||
                  headers={'X-Subject-Token': token},
 | 
			
		||||
                  expected_status=204)
 | 
			
		||||
 | 
			
		||||
        # delete the project, which should invalidate the token
 | 
			
		||||
        self.delete(
 | 
			
		||||
            '/projects/%(project_id)s' % {'project_id': self.projectA['id']})
 | 
			
		||||
 | 
			
		||||
        # user should no longer have access to the project
 | 
			
		||||
        self.head('/auth/tokens',
 | 
			
		||||
                  headers={'X-Subject-Token': token},
 | 
			
		||||
                  expected_status=401)
 | 
			
		||||
        resp = self.post(
 | 
			
		||||
            '/auth/tokens',
 | 
			
		||||
            body=self.build_authentication_request(
 | 
			
		||||
                user_id=self.user3['id'],
 | 
			
		||||
                password=self.user3['password'],
 | 
			
		||||
                project_id=self.projectA['id']),
 | 
			
		||||
            expected_status=401)
 | 
			
		||||
 | 
			
		||||
    def test_deleting_group_grant_revokes_tokens(self):
 | 
			
		||||
        """Test deleting a group grant revokes tokens.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user