diff --git a/keystone/contrib/revoke/backends/sql.py b/keystone/contrib/revoke/backends/sql.py index 6c12974665..8d50ac6cc0 100644 --- a/keystone/contrib/revoke/backends/sql.py +++ b/keystone/contrib/revoke/backends/sql.py @@ -34,6 +34,8 @@ class RevocationEvent(sql.ModelBase, sql.ModelDictMixin): issued_before = sql.Column(sql.DateTime(), nullable=False) expires_at = sql.Column(sql.DateTime()) revoked_at = sql.Column(sql.DateTime(), nullable=False) + audit_id = sql.Column(sql.String(32)) + audit_chain_id = sql.Column(sql.String(32)) class Revoke(revoke.Driver): diff --git a/keystone/contrib/revoke/core.py b/keystone/contrib/revoke/core.py index 1df99bd471..9fc240790a 100644 --- a/keystone/contrib/revoke/core.py +++ b/keystone/contrib/revoke/core.py @@ -26,6 +26,7 @@ from keystone import exception from keystone.i18n import _ from keystone import notifications from keystone.openstack.common import log +from keystone.openstack.common import versionutils CONF = config.CONF @@ -128,6 +129,8 @@ class Manager(manager.Manager): def revoke_by_user(self, user_id): return self.revoke(model.RevokeEvent(user_id=user_id)) + @versionutils.deprecated(as_of=versionutils.deprecated.JUNO, + remove_in=0) def revoke_by_expiration(self, user_id, expires_at, domain_id=None, project_id=None): @@ -144,6 +147,15 @@ class Manager(manager.Manager): domain_id=domain_id, project_id=project_id)) + def revoke_by_audit_id(self, audit_id): + self.revoke(model.RevokeEvent(audit_id=audit_id)) + + def revoke_by_audit_chain_id(self, audit_chain_id, project_id=None, + domain_id=None): + self.revoke(model.RevokeEvent(audit_chain_id=audit_chain_id, + domain_id=domain_id, + project_id=project_id)) + def revoke_by_grant(self, role_id, user_id=None, domain_id=None, project_id=None): self.revoke( diff --git a/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py b/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py new file mode 100644 index 0000000000..bee6fb2a5e --- /dev/null +++ b/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py @@ -0,0 +1,37 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sqlalchemy as sql + + +_TABLE_NAME = 'revocation_event' + + +def upgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + event_table = sql.Table(_TABLE_NAME, meta, autoload=True) + audit_id_column = sql.Column('audit_id', sql.String(32), nullable=True) + audit_chain_column = sql.Column('audit_chain_id', sql.String(32), + nullable=True) + event_table.create_column(audit_id_column) + event_table.create_column(audit_chain_column) + + +def downgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + event_table = sql.Table(_TABLE_NAME, meta, autoload=True) + event_table.drop_column('audit_id') + event_table.drop_column('audit_chain_id') diff --git a/keystone/contrib/revoke/model.py b/keystone/contrib/revoke/model.py index e2cd0be0a7..dc913e6ee2 100644 --- a/keystone/contrib/revoke/model.py +++ b/keystone/contrib/revoke/model.py @@ -18,6 +18,8 @@ from oslo.utils import timeutils _NAMES = ['trust_id', 'consumer_id', 'access_token_id', + 'audit_id', + 'audit_chain_id', 'expires_at', 'domain_id', 'project_id', @@ -86,6 +88,8 @@ class RevokeEvent(object): 'domain_id', 'domain_scope_id', 'project_id', + 'audit_id', + 'audit_chain_id', ] event = dict((key, self.__dict__[key]) for key in keys if self.__dict__[key] is not None) @@ -257,7 +261,10 @@ def build_token_values_v2(access, default_domain_id): token_values = { 'expires_at': timeutils.normalize_time(token_expires_at), 'issued_at': timeutils.normalize_time( - timeutils.parse_isotime(token_data['issued_at']))} + timeutils.parse_isotime(token_data['issued_at'])), + 'audit_id': token_data.get('audit_ids', [None])[0], + 'audit_chain_id': token_data.get('audit_ids', [None])[-1], + } token_values['user_id'] = access.get('user', {}).get('id') @@ -303,7 +310,10 @@ def build_token_values(token_data): token_values = { 'expires_at': timeutils.normalize_time(token_expires_at), 'issued_at': timeutils.normalize_time( - timeutils.parse_isotime(token_data['issued_at']))} + timeutils.parse_isotime(token_data['issued_at'])), + 'audit_id': token_data.get('audit_ids', [None])[0], + 'audit_chain_id': token_data.get('audit_ids', [None])[-1], + } user = token_data.get('user') if user is not None: diff --git a/keystone/tests/test_auth.py b/keystone/tests/test_auth.py index de16c8e7a0..22547f638c 100644 --- a/keystone/tests/test_auth.py +++ b/keystone/tests/test_auth.py @@ -30,6 +30,7 @@ from keystone import tests from keystone.tests import default_fixtures from keystone.tests.ksfixtures import database from keystone import token +from keystone.token import provider from keystone import trust @@ -489,6 +490,116 @@ class AuthWithToken(AuthTest): self.assertThat(audit_ids, matchers.HasLength(2)) self.assertThat(audit_ids[-1], matchers.Equals(starting_audit_id)) + def test_revoke_by_audit_chain_id_original_token(self): + self.config_fixture.config(group='token', revoke_by_id=False) + context = {} + + # get a token + body_dict = _build_user_auth(username='FOO', password='foo2') + unscoped_token = self.controller.authenticate(context, body_dict) + token_id = unscoped_token['access']['token']['id'] + # get a second token + body_dict = _build_user_auth(token=unscoped_token["access"]["token"]) + unscoped_token_2 = self.controller.authenticate(context, body_dict) + token_2_id = unscoped_token_2['access']['token']['id'] + + self.token_provider_api.revoke_token(token_id, revoke_chain=True) + + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_id) + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_2_id) + + def test_revoke_by_audit_chain_id_chained_token(self): + self.config_fixture.config(group='token', revoke_by_id=False) + context = {} + + # get a token + body_dict = _build_user_auth(username='FOO', password='foo2') + unscoped_token = self.controller.authenticate(context, body_dict) + token_id = unscoped_token['access']['token']['id'] + # get a second token + body_dict = _build_user_auth(token=unscoped_token["access"]["token"]) + unscoped_token_2 = self.controller.authenticate(context, body_dict) + token_2_id = unscoped_token_2['access']['token']['id'] + + self.token_provider_api.revoke_token(token_2_id, revoke_chain=True) + + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_id) + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_2_id) + + def _mock_audit_info(self, parent_audit_id): + # NOTE(morgainfainberg): The token model and other cases that are + # extracting the audit id expect 'None' if the audit id doesn't + # exist. This ensures that the audit_id is None and the + # audit_chain_id will also return None. + return [None, None] + + def test_revoke_with_no_audit_info(self): + self.config_fixture.config(group='token', revoke_by_id=False) + context = {} + + with mock.patch.object(provider, 'audit_info', self._mock_audit_info): + # get a token + body_dict = _build_user_auth(username='FOO', password='foo2') + unscoped_token = self.controller.authenticate(context, body_dict) + token_id = unscoped_token['access']['token']['id'] + # get a second token + body_dict = _build_user_auth( + token=unscoped_token['access']['token']) + unscoped_token_2 = self.controller.authenticate(context, body_dict) + token_2_id = unscoped_token_2['access']['token']['id'] + + self.token_provider_api.revoke_token(token_id, revoke_chain=True) + + revoke_events = self.revoke_api.get_events() + self.assertThat(revoke_events, matchers.HasLength(1)) + revoke_event = revoke_events[0].to_dict() + self.assertIn('expires_at', revoke_event) + self.assertEqual(unscoped_token_2['access']['token']['expires'], + revoke_event['expires_at']) + + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_id) + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_2_id) + + # get a new token, with no audit info + body_dict = _build_user_auth(username='FOO', password='foo2') + unscoped_token = self.controller.authenticate(context, body_dict) + token_id = unscoped_token['access']['token']['id'] + # get a second token + body_dict = _build_user_auth( + token=unscoped_token['access']['token']) + unscoped_token_2 = self.controller.authenticate(context, body_dict) + token_2_id = unscoped_token_2['access']['token']['id'] + + # Revoke by audit_id, no audit_info means both parent and child + # token are revoked. + self.token_provider_api.revoke_token(token_id) + + revoke_events = self.revoke_api.get_events() + self.assertThat(revoke_events, matchers.HasLength(2)) + revoke_event = revoke_events[1].to_dict() + self.assertIn('expires_at', revoke_event) + self.assertEqual(unscoped_token_2['access']['token']['expires'], + revoke_event['expires_at']) + + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_id) + self.assertRaises(exception.TokenNotFound, + self.token_provider_api.validate_v2_token, + token_id=token_2_id) + class AuthWithPasswordCredentials(AuthTest): def test_auth_invalid_user(self): diff --git a/keystone/tests/test_revoke.py b/keystone/tests/test_revoke.py index 07d0c1c969..d0a3089ffc 100644 --- a/keystone/tests/test_revoke.py +++ b/keystone/tests/test_revoke.py @@ -23,6 +23,7 @@ from keystone.contrib.revoke import model from keystone import exception from keystone import tests from keystone.tests import test_backend_sql +from keystone.token import provider def _new_id(): @@ -92,7 +93,7 @@ def _matches(event, token_values): # rest of the logic. attribute_names = ['project_id', 'expires_at', 'trust_id', 'consumer_id', - 'access_token_id'] + 'access_token_id', 'audit_id', 'audit_chain_id'] for attribute_name in attribute_names: if getattr(event, attribute_name) is not None: if (getattr(event, attribute_name) != @@ -267,6 +268,22 @@ class RevokeTreeTests(tests.TestCase): return self.tree.add_event( model.RevokeEvent(user_id=user_id)) + def _revoke_by_audit_id(self, audit_id): + event = self.tree.add_event( + model.RevokeEvent(audit_id=audit_id)) + self.events.append(event) + return event + + def _revoke_by_audit_chain_id(self, audit_chain_id, project_id=None, + domain_id=None): + event = self.tree.add_event( + model.RevokeEvent(audit_chain_id=audit_chain_id, + project_id=project_id, + domain_id=domain_id) + ) + self.events.append(event) + return event + def _revoke_by_expiration(self, user_id, expires_at, project_id=None, domain_id=None): event = self.tree.add_event( @@ -355,6 +372,45 @@ class RevokeTreeTests(tests.TestCase): self.removeEvent(event) self._assertTokenNotRevoked(token_data_1) + def test_revoke_by_audit_id(self): + audit_id = provider.audit_info(parent_audit_id=None)[0] + token_data_1 = _sample_blank_token() + # Audit ID and Audit Chain ID are populated with the same value + # if the token is an original token + token_data_1['audit_id'] = audit_id + token_data_1['audit_chain_id'] = audit_id + event = self._revoke_by_audit_id(audit_id) + self._assertTokenRevoked(token_data_1) + + audit_id_2 = provider.audit_info(parent_audit_id=audit_id)[0] + token_data_2 = _sample_blank_token() + token_data_2['audit_id'] = audit_id_2 + token_data_2['audit_chain_id'] = audit_id + self._assertTokenNotRevoked(token_data_2) + + self.removeEvent(event) + self._assertTokenNotRevoked(token_data_1) + + def test_revoke_by_audit_chain_id(self): + audit_id = provider.audit_info(parent_audit_id=None)[0] + token_data_1 = _sample_blank_token() + # Audit ID and Audit Chain ID are populated with the same value + # if the token is an original token + token_data_1['audit_id'] = audit_id + token_data_1['audit_chain_id'] = audit_id + event = self._revoke_by_audit_chain_id(audit_id) + self._assertTokenRevoked(token_data_1) + + audit_id_2 = provider.audit_info(parent_audit_id=audit_id)[0] + token_data_2 = _sample_blank_token() + token_data_2['audit_id'] = audit_id_2 + token_data_2['audit_chain_id'] = audit_id + self._assertTokenRevoked(token_data_2) + + self.removeEvent(event) + self._assertTokenNotRevoked(token_data_1) + self._assertTokenNotRevoked(token_data_2) + def test_by_user_project(self): # When a user has a project-scoped token and the project-scoped token # is revoked then the token is revoked. @@ -515,18 +571,24 @@ class RevokeTreeTests(tests.TestCase): self.assertEqual(turn + 1, len(self.tree.revoke_map ['trust_id=*'] ['consumer_id=*'] - ['access_token_id=*'])) + ['access_token_id=*'] + ['audit_id=*'] + ['audit_chain_id=*'])) # two different functions add domain_ids, +1 for None self.assertEqual(2 * turn + 1, len(self.tree.revoke_map ['trust_id=*'] ['consumer_id=*'] ['access_token_id=*'] + ['audit_id=*'] + ['audit_chain_id=*'] ['expires_at=*'])) # two different functions add project_ids, +1 for None self.assertEqual(2 * turn + 1, len(self.tree.revoke_map ['trust_id=*'] ['consumer_id=*'] ['access_token_id=*'] + ['audit_id=*'] + ['audit_chain_id=*'] ['expires_at=*'] ['domain_id=*'])) # 10 users added @@ -534,6 +596,8 @@ class RevokeTreeTests(tests.TestCase): ['trust_id=*'] ['consumer_id=*'] ['access_token_id=*'] + ['audit_id=*'] + ['audit_chain_id=*'] ['expires_at=*'] ['domain_id=*'] ['project_id=*'])) @@ -554,7 +618,9 @@ class RevokeTreeTests(tests.TestCase): self.assertEqual(i + 2, len(self.tree.revoke_map ['trust_id=*'] ['consumer_id=*'] - ['access_token_id=*']), + ['access_token_id=*'] + ['audit_id=*'] + ['audit_chain_id=*']), 'adding %s to %s' % (args, self.tree.revoke_map)) diff --git a/keystone/tests/test_sql_upgrade.py b/keystone/tests/test_sql_upgrade.py index 077ca88d75..eef4b7a7fb 100644 --- a/keystone/tests/test_sql_upgrade.py +++ b/keystone/tests/test_sql_upgrade.py @@ -37,6 +37,7 @@ from migrate.versioning import api as versioning_api from oslo.db import exception as db_exception from oslo.db.sqlalchemy import migration from oslo.db.sqlalchemy import session as db_session +import six import sqlalchemy.exc from keystone.assignment.backends import sql as assignment_sql @@ -45,6 +46,7 @@ from keystone.common.sql import migrate_repo from keystone.common.sql import migration_helpers from keystone import config from keystone.contrib import federation +from keystone.contrib import revoke from keystone import exception from keystone import tests from keystone.tests import default_fixtures @@ -119,10 +121,14 @@ INITIAL_EXTENSION_TABLE_STRUCTURE = { 'revocation_event': [ 'id', 'domain_id', 'project_id', 'user_id', 'role_id', 'trust_id', 'consumer_id', 'access_token_id', - 'issued_before', 'expires_at', 'revoked_at', + 'issued_before', 'expires_at', 'revoked_at', 'audit_id', + 'audit_chain_id', ], } +EXTENSIONS = {'federation': federation, + 'revoke': revoke} + class SqlMigrateBase(tests.SQLDriverOverrides, tests.TestCase): def initialize_sql(self): @@ -1413,18 +1419,38 @@ class VersionTests(SqlMigrateBase): def test_extension_initial(self): """When get the initial version of an extension, it's 0.""" - abs_path = migration_helpers.find_migrate_repo(federation) - migration.db_version_control(sql.get_engine(), abs_path) - version = migration_helpers.get_db_version(extension='federation') - self.assertEqual(0, version) + for name, extension in six.iteritems(EXTENSIONS): + abs_path = migration_helpers.find_migrate_repo(extension) + migration.db_version_control(sql.get_engine(), abs_path) + version = migration_helpers.get_db_version(extension=name) + self.assertEqual(0, version, + 'Migrate version for %s is not 0' % name) def test_extension_migrated(self): """When get the version after migrating an extension, it's not 0.""" - abs_path = migration_helpers.find_migrate_repo(federation) - migration.db_version_control(sql.get_engine(), abs_path) - migration.db_sync(sql.get_engine(), abs_path) - version = migration_helpers.get_db_version(extension='federation') - self.assertTrue(version > 0, "Version didn't change after migrated?") + for name, extension in six.iteritems(EXTENSIONS): + abs_path = migration_helpers.find_migrate_repo(extension) + migration.db_version_control(sql.get_engine(), abs_path) + migration.db_sync(sql.get_engine(), abs_path) + version = migration_helpers.get_db_version(extension=name) + self.assertTrue( + version > 0, + "Version for %s didn't change after migrated?" % name) + + def test_extension_downgraded(self): + """When get the version after downgrading an extension, it is 0.""" + for name, extension in six.iteritems(EXTENSIONS): + abs_path = migration_helpers.find_migrate_repo(extension) + migration.db_version_control(sql.get_engine(), abs_path) + migration.db_sync(sql.get_engine(), abs_path) + version = migration_helpers.get_db_version(extension=name) + self.assertTrue( + version > 0, + "Version for %s didn't change after migrated?" % name) + migration.db_sync(sql.get_engine(), abs_path, version=0) + version = migration_helpers.get_db_version(extension=name) + self.assertEqual(0, version, + 'Migrate version for %s is not 0' % name) def test_unexpected_extension(self): """The version for an extension that doesn't exist raises ImportError. diff --git a/keystone/tests/test_v3_auth.py b/keystone/tests/test_v3_auth.py index e1884cc0fd..0b2101ffa6 100644 --- a/keystone/tests/test_v3_auth.py +++ b/keystone/tests/test_v3_auth.py @@ -19,6 +19,7 @@ import uuid from keystoneclient.common import cms from oslo.utils import timeutils +import six from testtools import matchers from testtools import testcase @@ -1341,48 +1342,45 @@ class TestTokenRevokeApi(TestTokenRevokeById): expected_response = {'events': [{'domain_id': domain_id}]} self.assertEqual(expected_response, events_response) - def assertValidRevokedTokenResponse(self, events_response, user_id, - project_id=None): + def assertValidRevokedTokenResponse(self, events_response, **kwargs): events = events_response['events'] self.assertEqual(1, len(events)) - self.assertEqual(user_id, events[0]['user_id']) - if project_id: - self.assertEqual(project_id, events[0]['project_id']) - self.assertIsNotNone(events[0]['expires_at']) + for k, v in six.iteritems(kwargs): + self.assertEqual(v, events[0].get(k)) self.assertIsNotNone(events[0]['issued_before']) self.assertIsNotNone(events_response['links']) - del (events_response['events'][0]['expires_at']) del (events_response['events'][0]['issued_before']) del (events_response['links']) - expected_event_data = {'user_id': user_id} - if project_id: - expected_event_data['project_id'] = project_id - expected_response = {'events': [expected_event_data]} + expected_response = {'events': [kwargs]} self.assertEqual(expected_response, events_response) def test_revoke_token(self): scoped_token = self.get_scoped_token() headers = {'X-Subject-Token': scoped_token} - self.head('/auth/tokens', headers=headers, expected_status=200) + response = self.get('/auth/tokens', headers=headers, + expected_status=200).json_body['token'] + self.delete('/auth/tokens', headers=headers, expected_status=204) self.head('/auth/tokens', headers=headers, expected_status=404) events_response = self.get('/OS-REVOKE/events', expected_status=200).json_body - self.assertValidRevokedTokenResponse(events_response, self.user['id'], - project_id=self.project['id']) + self.assertValidRevokedTokenResponse(events_response, + audit_id=response['audit_ids'][0]) def test_revoke_v2_token(self): token = self.get_v2_token() headers = {'X-Subject-Token': token} - self.head('/auth/tokens', headers=headers, expected_status=200) + response = self.get('/auth/tokens', headers=headers, + expected_status=200).json_body['token'] self.delete('/auth/tokens', headers=headers, expected_status=204) self.head('/auth/tokens', headers=headers, expected_status=404) events_response = self.get('/OS-REVOKE/events', expected_status=200).json_body - self.assertValidRevokedTokenResponse(events_response, - self.default_domain_user['id']) + self.assertValidRevokedTokenResponse( + events_response, + audit_id=response['audit_ids'][0]) def test_revoke_by_id_false_410(self): self.get('/auth/tokens/OS-PKI/revoked', expected_status=410) @@ -1414,19 +1412,30 @@ class TestTokenRevokeApi(TestTokenRevokeById): self.assertDomainInList(events, self.domainA['id']) - def assertUserAndExpiryInList(self, events, user_id, expires_at): + def assertEventDataInList(self, events, **kwargs): found = False for e in events: - - # Timestamps in the event list are accurate to second. - expires_at = timeutils.parse_isotime(expires_at) - expires_at = timeutils.isotime(expires_at) - - if e['user_id'] == user_id and e['expires_at'] == expires_at: + for key, value in six.iteritems(kwargs): + try: + if e[key] != value: + break + except KeyError: + # Break the loop and present a nice error instead of + # KeyError + break + else: + # If the value of the event[key] matches the value of the kwarg + # for each item in kwargs, the event was fully matched and + # the assertTrue below should succeed. found = True self.assertTrue(found, - 'event with correct user_id %s and expires_at value ' - 'not in list' % user_id) + 'event with correct values not in list, expected to ' + 'find event with key-value pairs. Expected: ' + '"%(expected)s" Events: "%(events)s"' % + {'expected': ','.join( + ["'%s=%s'" % (k, v) for k, v in six.iteritems( + kwargs)]), + 'events': events}) def test_list_delete_token_shows_in_event_list(self): self.role_data_fixtures() @@ -1456,11 +1465,9 @@ class TestTokenRevokeApi(TestTokenRevokeById): expected_status=200).json_body events = events_response['events'] self.assertEqual(1, len(events)) - self.assertUserAndExpiryInList(events, - token2['user']['id'], - token2['expires_at']) - self.assertValidRevokedTokenResponse(events_response, self.user['id'], - project_id=self.project['id']) + self.assertEventDataInList( + events, + audit_id=token2['audit_ids'][1]) self.head('/auth/tokens', headers=headers, expected_status=404) self.head('/auth/tokens', headers=headers2, expected_status=200) self.head('/auth/tokens', headers=headers3, expected_status=200) diff --git a/keystone/token/provider.py b/keystone/token/provider.py index 6b1e38a308..2c6ff80f87 100644 --- a/keystone/token/provider.py +++ b/keystone/token/provider.py @@ -411,31 +411,43 @@ class Manager(manager.Manager): self._validate_v2_token.invalidate(self, token_id) self._validate_v3_token.invalidate(self, token_id) - def revoke_token(self, token_id): + def revoke_token(self, token_id, revoke_chain=False): if self.revoke_api: - user_id = None - expires_at = None - domain_id = None + revoke_by_expires = False project_id = None + domain_id = None - token_ref = self.persistence.get_token(token_id) - version = self.driver.get_token_version(token_ref) + token_ref = token_model.KeystoneToken( + token_id=token_id, + token_data=self.validate_token(token_id)) - if version == self.V3: - user_id = token_ref['user']['id'] - expires_at = token_ref['expires'] + user_id = token_ref.user_id + expires_at = token_ref.expires + audit_id = token_ref.audit_id + audit_chain_id = token_ref.audit_chain_id + if token_ref.project_scoped: + project_id = token_ref.project_id + if token_ref.domain_scoped: + domain_id = token_ref.domain_id - token_data = token_ref['token_data']['token'] - project_id = token_data.get('project', {}).get('id') - domain_id = token_data.get('domain', {}).get('id') - elif version == self.V2: - user_id = token_ref['user_id'] - expires_at = token_ref['expires'] - project_id = (token_ref.get('tenant') or {}).get('id') + if audit_id is None and not revoke_chain: + LOG.debug('Received token with no audit_id.') + revoke_by_expires = True - self.revoke_api.revoke_by_expiration(user_id, expires_at, - project_id=project_id, - domain_id=domain_id) + if audit_chain_id is None and revoke_chain: + LOG.debug('Received token with no audit_chain_id.', token_id) + revoke_by_expires = True + + if revoke_by_expires: + self.revoke_api.revoke_by_expiration(user_id, expires_at, + project_id=project_id, + domain_id=domain_id) + elif revoke_chain: + self.revoke_api.revoke_by_audit_chain_id(audit_chain_id, + project_id=project_id, + domain_id=domain_id) + else: + self.revoke_api.revoke_by_audit_id(audit_id) if CONF.token.revoke_by_id: self.persistence.delete_token(token_id=token_id)