diff options
Diffstat (limited to 'keystone-moon/keystone/token/persistence/core.py')
-rw-r--r-- | keystone-moon/keystone/token/persistence/core.py | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/keystone-moon/keystone/token/persistence/core.py b/keystone-moon/keystone/token/persistence/core.py new file mode 100644 index 00000000..19f0df35 --- /dev/null +++ b/keystone-moon/keystone/token/persistence/core.py @@ -0,0 +1,361 @@ +# Copyright 2012 OpenStack Foundation +# +# 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. + +"""Main entry point into the Token persistence service.""" + +import abc +import copy + +from oslo_config import cfg +from oslo_log import log +from oslo_utils import timeutils +import six + +from keystone.common import cache +from keystone.common import dependency +from keystone.common import manager +from keystone import exception +from keystone.i18n import _LW + + +CONF = cfg.CONF +LOG = log.getLogger(__name__) +MEMOIZE = cache.get_memoization_decorator(section='token') +REVOCATION_MEMOIZE = cache.get_memoization_decorator( + section='token', expiration_section='revoke') + + +@dependency.requires('assignment_api', 'identity_api', 'resource_api', + 'token_provider_api', 'trust_api') +class PersistenceManager(manager.Manager): + """Default pivot point for the Token backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + + """ + + def __init__(self): + super(PersistenceManager, self).__init__(CONF.token.driver) + + def _assert_valid(self, token_id, token_ref): + """Raise TokenNotFound if the token is expired.""" + current_time = timeutils.normalize_time(timeutils.utcnow()) + expires = token_ref.get('expires') + if not expires or current_time > timeutils.normalize_time(expires): + raise exception.TokenNotFound(token_id=token_id) + + def get_token(self, token_id): + if not token_id: + # NOTE(morganfainberg): There are cases when the + # context['token_id'] will in-fact be None. This also saves + # a round-trip to the backend if we don't have a token_id. + raise exception.TokenNotFound(token_id='') + unique_id = self.token_provider_api.unique_id(token_id) + token_ref = self._get_token(unique_id) + # NOTE(morganfainberg): Lift expired checking to the manager, there is + # no reason to make the drivers implement this check. With caching, + # self._get_token could return an expired token. Make sure we behave + # as expected and raise TokenNotFound on those instances. + self._assert_valid(token_id, token_ref) + return token_ref + + @MEMOIZE + def _get_token(self, token_id): + # Only ever use the "unique" id in the cache key. + return self.driver.get_token(token_id) + + def create_token(self, token_id, data): + unique_id = self.token_provider_api.unique_id(token_id) + data_copy = copy.deepcopy(data) + data_copy['id'] = unique_id + ret = self.driver.create_token(unique_id, data_copy) + if MEMOIZE.should_cache(ret): + # NOTE(morganfainberg): when doing a cache set, you must pass the + # same arguments through, the same as invalidate (this includes + # "self"). First argument is always the value to be cached + self._get_token.set(ret, self, unique_id) + return ret + + def delete_token(self, token_id): + if not CONF.token.revoke_by_id: + return + unique_id = self.token_provider_api.unique_id(token_id) + self.driver.delete_token(unique_id) + self._invalidate_individual_token_cache(unique_id) + self.invalidate_revocation_list() + + def delete_tokens(self, user_id, tenant_id=None, trust_id=None, + consumer_id=None): + if not CONF.token.revoke_by_id: + return + token_list = self.driver._list_tokens(user_id, tenant_id, trust_id, + consumer_id) + self.driver.delete_tokens(user_id, tenant_id, trust_id, consumer_id) + for token_id in token_list: + unique_id = self.token_provider_api.unique_id(token_id) + self._invalidate_individual_token_cache(unique_id) + self.invalidate_revocation_list() + + @REVOCATION_MEMOIZE + def list_revoked_tokens(self): + return self.driver.list_revoked_tokens() + + def invalidate_revocation_list(self): + # NOTE(morganfainberg): Note that ``self`` needs to be passed to + # invalidate() because of the way the invalidation method works on + # determining cache-keys. + self.list_revoked_tokens.invalidate(self) + + def delete_tokens_for_domain(self, domain_id): + """Delete all tokens for a given domain. + + It will delete all the project-scoped tokens for the projects + that are owned by the given domain, as well as any tokens issued + to users that are owned by this domain. + + However, deletion of domain_scoped tokens will still need to be + implemented as stated in TODO below. + """ + if not CONF.token.revoke_by_id: + return + projects = self.resource_api.list_projects() + for project in projects: + if project['domain_id'] == domain_id: + for user_id in self.assignment_api.list_user_ids_for_project( + project['id']): + self.delete_tokens_for_user(user_id, project['id']) + # TODO(morganfainberg): implement deletion of domain_scoped tokens. + + users = self.identity_api.list_users(domain_id) + user_ids = (user['id'] for user in users) + self.delete_tokens_for_users(user_ids) + + def delete_tokens_for_user(self, user_id, project_id=None): + """Delete all tokens for a given user or user-project combination. + + This method adds in the extra logic for handling trust-scoped token + revocations in a single call instead of needing to explicitly handle + trusts in the caller's logic. + """ + if not CONF.token.revoke_by_id: + return + self.delete_tokens(user_id, tenant_id=project_id) + for trust in self.trust_api.list_trusts_for_trustee(user_id): + # Ensure we revoke tokens associated to the trust / project + # user_id combination. + self.delete_tokens(user_id, trust_id=trust['id'], + tenant_id=project_id) + for trust in self.trust_api.list_trusts_for_trustor(user_id): + # Ensure we revoke tokens associated to the trust / project / + # user_id combination where the user_id is the trustor. + + # NOTE(morganfainberg): This revocation is a bit coarse, but it + # covers a number of cases such as disabling of the trustor user, + # deletion of the trustor user (for any number of reasons). It + # might make sense to refine this and be more surgical on the + # deletions (e.g. don't revoke tokens for the trusts when the + # trustor changes password). For now, to maintain previous + # functionality, this will continue to be a bit overzealous on + # revocations. + self.delete_tokens(trust['trustee_user_id'], trust_id=trust['id'], + tenant_id=project_id) + + def delete_tokens_for_users(self, user_ids, project_id=None): + """Delete all tokens for a list of user_ids. + + :param user_ids: list of user identifiers + :param project_id: optional project identifier + """ + if not CONF.token.revoke_by_id: + return + for user_id in user_ids: + self.delete_tokens_for_user(user_id, project_id=project_id) + + def _invalidate_individual_token_cache(self, token_id): + # NOTE(morganfainberg): invalidate takes the exact same arguments as + # the normal method, this means we need to pass "self" in (which gets + # stripped off). + + # FIXME(morganfainberg): Does this cache actually need to be + # invalidated? We maintain a cached revocation list, which should be + # consulted before accepting a token as valid. For now we will + # do the explicit individual token invalidation. + self._get_token.invalidate(self, token_id) + self.token_provider_api.invalidate_individual_token_cache(token_id) + + +# NOTE(morganfainberg): @dependency.optional() is required here to ensure the +# class-level optional dependency control attribute is populated as empty +# this is because of the override of .__getattr__ and ensures that if the +# optional dependency injector changes attributes, this class doesn't break. +@dependency.optional() +@dependency.requires('token_provider_api') +@dependency.provider('token_api') +class Manager(object): + """The token_api provider. + + This class is a proxy class to the token_provider_api's persistence + manager. + """ + def __init__(self): + # NOTE(morganfainberg): __init__ is required for dependency processing. + super(Manager, self).__init__() + + def __getattr__(self, item): + """Forward calls to the `token_provider_api` persistence manager.""" + + # NOTE(morganfainberg): Prevent infinite recursion, raise an + # AttributeError for 'token_provider_api' ensuring that the dep + # injection doesn't infinitely try and lookup self.token_provider_api + # on _process_dependencies. This doesn't need an exception string as + # it should only ever be hit on instantiation. + if item == 'token_provider_api': + raise AttributeError() + + f = getattr(self.token_provider_api._persistence, item) + LOG.warning(_LW('`token_api.%s` is deprecated as of Juno in favor of ' + 'utilizing methods on `token_provider_api` and may be ' + 'removed in Kilo.'), item) + setattr(self, item, f) + return f + + +@six.add_metaclass(abc.ABCMeta) +class Driver(object): + """Interface description for a Token driver.""" + + @abc.abstractmethod + def get_token(self, token_id): + """Get a token by id. + + :param token_id: identity of the token + :type token_id: string + :returns: token_ref + :raises: keystone.exception.TokenNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def create_token(self, token_id, data): + """Create a token by id and data. + + :param token_id: identity of the token + :type token_id: string + :param data: dictionary with additional reference information + + :: + + { + expires='' + id=token_id, + user=user_ref, + tenant=tenant_ref, + metadata=metadata_ref + } + + :type data: dict + :returns: token_ref or None. + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_token(self, token_id): + """Deletes a token by id. + + :param token_id: identity of the token + :type token_id: string + :returns: None. + :raises: keystone.exception.TokenNotFound + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def delete_tokens(self, user_id, tenant_id=None, trust_id=None, + consumer_id=None): + """Deletes tokens by user. + + If the tenant_id is not None, only delete the tokens by user id under + the specified tenant. + + If the trust_id is not None, it will be used to query tokens and the + user_id will be ignored. + + If the consumer_id is not None, only delete the tokens by consumer id + that match the specified consumer id. + + :param user_id: identity of user + :type user_id: string + :param tenant_id: identity of the tenant + :type tenant_id: string + :param trust_id: identity of the trust + :type trust_id: string + :param consumer_id: identity of the consumer + :type consumer_id: string + :returns: None. + :raises: keystone.exception.TokenNotFound + + """ + if not CONF.token.revoke_by_id: + return + token_list = self._list_tokens(user_id, + tenant_id=tenant_id, + trust_id=trust_id, + consumer_id=consumer_id) + + for token in token_list: + try: + self.delete_token(token) + except exception.NotFound: + pass + + @abc.abstractmethod + def _list_tokens(self, user_id, tenant_id=None, trust_id=None, + consumer_id=None): + """Returns a list of current token_id's for a user + + This is effectively a private method only used by the ``delete_tokens`` + method and should not be called by anything outside of the + ``token_api`` manager or the token driver itself. + + :param user_id: identity of the user + :type user_id: string + :param tenant_id: identity of the tenant + :type tenant_id: string + :param trust_id: identity of the trust + :type trust_id: string + :param consumer_id: identity of the consumer + :type consumer_id: string + :returns: list of token_id's + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def list_revoked_tokens(self): + """Returns a list of all revoked tokens + + :returns: list of token_id's + + """ + raise exception.NotImplemented() # pragma: no cover + + @abc.abstractmethod + def flush_expired_tokens(self): + """Archive or delete tokens that have expired. + """ + raise exception.NotImplemented() # pragma: no cover |