Pre-cache new tokens
Since tokens are most often used right after being created, cache them to bypass redundant validation. The patch uses dogpile.cache internal functionality so some calls may look strange Implements bp pre-cache-tokens Change-Id: I2e720eed6b0066738181afd1cbf73c5ff4d876f5
This commit is contained in:
parent
0ae6d0924f
commit
7260b55cfc
@ -157,6 +157,13 @@ directly assigned to the token's scope, but are instead linked implicitly to
|
||||
other role assignments.
|
||||
"""))
|
||||
|
||||
cache_on_issue = cfg.BoolOpt(
|
||||
'cache_on_issue',
|
||||
default=False,
|
||||
help=utils.fmt("""
|
||||
Enable storing issued token data to token validation cache so that first token
|
||||
validation doesn't actually cause full validation cycle.
|
||||
"""))
|
||||
|
||||
GROUP_NAME = __name__.split('.')[-1]
|
||||
ALL_OPTS = [
|
||||
@ -171,6 +178,7 @@ ALL_OPTS = [
|
||||
allow_rescope_scoped_token,
|
||||
hash_algorithm,
|
||||
infer_roles,
|
||||
cache_on_issue,
|
||||
]
|
||||
|
||||
|
||||
|
@ -1328,7 +1328,8 @@ class FernetAuthWithTrust(AuthWithTrust, AuthTest):
|
||||
|
||||
def config_overrides(self):
|
||||
super(FernetAuthWithTrust, self).config_overrides()
|
||||
self.config_fixture.config(group='token', provider='fernet')
|
||||
self.config_fixture.config(group='token', provider='fernet',
|
||||
cache_on_issue=True)
|
||||
self.useFixture(
|
||||
ksfixtures.KeyRepository(
|
||||
self.config_fixture,
|
||||
@ -1346,6 +1347,14 @@ class FernetAuthWithTrust(AuthWithTrust, AuthTest):
|
||||
msg = 'The Fernet token provider does not support token persistence'
|
||||
self.skipTest(msg)
|
||||
|
||||
def test_delete_trust_revokes_token(self):
|
||||
# NOTE(amakarov): have to override this for Fernet as TokenNotFound
|
||||
# can't be raised for non-persistent token, but deleted trust will
|
||||
# cause TrustNotFound exception.
|
||||
self.assertRaises(
|
||||
exception.TrustNotFound,
|
||||
super(FernetAuthWithTrust, self).test_delete_trust_revokes_token)
|
||||
|
||||
|
||||
class TokenExpirationTest(AuthTest):
|
||||
|
||||
|
@ -2426,7 +2426,8 @@ class TestFernetTokenAPIs(test_v3.RestfulTestCase, TokenAPITests,
|
||||
TokenDataTests):
|
||||
def config_overrides(self):
|
||||
super(TestFernetTokenAPIs, self).config_overrides()
|
||||
self.config_fixture.config(group='token', provider='fernet')
|
||||
self.config_fixture.config(group='token', provider='fernet',
|
||||
cache_on_issue=True)
|
||||
self.useFixture(
|
||||
ksfixtures.KeyRepository(
|
||||
self.config_fixture,
|
||||
@ -2504,6 +2505,13 @@ class TestFernetTokenAPIs(test_v3.RestfulTestCase, TokenAPITests,
|
||||
self.v3_create_token(auth_data,
|
||||
expected_status=http_client.NOT_IMPLEMENTED)
|
||||
|
||||
def test_trust_scoped_token_is_invalid_after_disabling_trustor(self):
|
||||
# NOTE(amakarov): have to override this test for non-persistent tokens
|
||||
# as TokenNotFound exception makes no sense for those.
|
||||
self.assertRaises(
|
||||
exception.Forbidden, super(TestFernetTokenAPIs, self)
|
||||
.test_trust_scoped_token_is_invalid_after_disabling_trustor)
|
||||
|
||||
|
||||
class TestTokenRevokeSelfAndAdmin(test_v3.RestfulTestCase):
|
||||
"""Test token revoke using v3 Identity API by token owner and admin."""
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import abc
|
||||
import base64
|
||||
import copy
|
||||
import datetime
|
||||
import sys
|
||||
import uuid
|
||||
@ -379,6 +380,16 @@ class Manager(manager.Manager):
|
||||
token_version=self.V2)
|
||||
self._create_token(token_id, data)
|
||||
|
||||
# NOTE(amakarov): TOKENS_REGION is to be passed to serve as
|
||||
# required positional "self" argument. It's ignored, so I've put
|
||||
# it here for convenience - any placeholder is fine.
|
||||
# NOTE(amakarov): v3 token data can be converted to v2.0 version,
|
||||
# so v2.0 token validation cache can also be populated. However it
|
||||
# isn't reflexive: there is no way to populate v3 validation cache
|
||||
# on issuing a token using v2.0 API.
|
||||
if CONF.token.cache_on_issue:
|
||||
self._validate_v2_token.set(token_data, TOKENS_REGION, token_id)
|
||||
|
||||
return token_id, token_data
|
||||
|
||||
def issue_v3_token(self, user_id, method_names, expires_at=None,
|
||||
@ -419,6 +430,27 @@ class Manager(manager.Manager):
|
||||
token_version=self.V3)
|
||||
if self._needs_persistence:
|
||||
self._create_token(token_id, data)
|
||||
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): here and above TOKENS_REGION is to be passed
|
||||
# to serve as required positional "self" argument. It's ignored,
|
||||
# so I've put it here for convenience - any placeholder is fine.
|
||||
self._validate_v3_token.set(token_data, TOKENS_REGION, token_id)
|
||||
self._validate_token.set(token_data, TOKENS_REGION, token_id)
|
||||
self.validate_non_persistent_token.set(
|
||||
token_data, TOKENS_REGION, token_id)
|
||||
|
||||
try:
|
||||
v2_helper = providers.common.V2TokenDataHelper()
|
||||
v2_token_data = v2_helper.v3_to_v2_token(
|
||||
copy.deepcopy(token_data), token_id)
|
||||
except exception.Unauthorized:
|
||||
# Ignore trust and oauth tokens
|
||||
pass
|
||||
else:
|
||||
self._validate_v2_token.set(
|
||||
v2_token_data, TOKENS_REGION, token_id)
|
||||
|
||||
return token_id, token_data
|
||||
|
||||
def invalidate_individual_token_cache(self, token_id):
|
||||
@ -467,18 +499,27 @@ class Manager(manager.Manager):
|
||||
trust = self.trust_api.get_trust(trust_id, deleted=True)
|
||||
self._persistence.delete_tokens(user_id=trust['trustor_user_id'],
|
||||
trust_id=trust_id)
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): preserving behavior
|
||||
TOKENS_REGION.invalidate()
|
||||
|
||||
def _delete_user_tokens_callback(self, service, resource_type, operation,
|
||||
payload):
|
||||
if CONF.token.revoke_by_id:
|
||||
user_id = payload['resource_info']
|
||||
self._persistence.delete_tokens_for_user(user_id)
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): preserving behavior
|
||||
TOKENS_REGION.invalidate()
|
||||
|
||||
def _delete_domain_tokens_callback(self, service, resource_type,
|
||||
operation, payload):
|
||||
if CONF.token.revoke_by_id:
|
||||
domain_id = payload['resource_info']
|
||||
self._persistence.delete_tokens_for_domain(domain_id=domain_id)
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): preserving behavior
|
||||
TOKENS_REGION.invalidate()
|
||||
|
||||
def _delete_user_project_tokens_callback(self, service, resource_type,
|
||||
operation, payload):
|
||||
@ -487,6 +528,9 @@ class Manager(manager.Manager):
|
||||
project_id = payload['resource_info']['project_id']
|
||||
self._persistence.delete_tokens_for_user(user_id=user_id,
|
||||
project_id=project_id)
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): preserving behavior
|
||||
TOKENS_REGION.invalidate()
|
||||
|
||||
def _delete_project_tokens_callback(self, service, resource_type,
|
||||
operation, payload):
|
||||
@ -495,6 +539,9 @@ class Manager(manager.Manager):
|
||||
self._persistence.delete_tokens_for_users(
|
||||
self.assignment_api.list_user_ids_for_project(project_id),
|
||||
project_id=project_id)
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): preserving behavior
|
||||
TOKENS_REGION.invalidate()
|
||||
|
||||
def _delete_user_oauth_consumer_tokens_callback(self, service,
|
||||
resource_type, operation,
|
||||
@ -504,6 +551,9 @@ class Manager(manager.Manager):
|
||||
consumer_id = payload['resource_info']['consumer_id']
|
||||
self._persistence.delete_tokens(user_id=user_id,
|
||||
consumer_id=consumer_id)
|
||||
if CONF.token.cache_on_issue:
|
||||
# NOTE(amakarov): preserving behavior
|
||||
TOKENS_REGION.invalidate()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
prelude: >
|
||||
Tokens can now be cached when issued.
|
||||
features:
|
||||
- Add ``cache_on_issue`` flag to ``[token]`` section that enables
|
||||
placing issued tokens to validation cache thus reducing the first
|
||||
validation time as if token is already validated and token data cached.
|
Loading…
Reference in New Issue
Block a user