diff options
Diffstat (limited to 'keystone-moon/keystone/token')
22 files changed, 0 insertions, 4609 deletions
diff --git a/keystone-moon/keystone/token/__init__.py b/keystone-moon/keystone/token/__init__.py deleted file mode 100644 index f85ffc79..00000000 --- a/keystone-moon/keystone/token/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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. - -from keystone.token import controllers # noqa -from keystone.token import persistence # noqa -from keystone.token import provider # noqa diff --git a/keystone-moon/keystone/token/_simple_cert.py b/keystone-moon/keystone/token/_simple_cert.py deleted file mode 100644 index 9c369255..00000000 --- a/keystone-moon/keystone/token/_simple_cert.py +++ /dev/null @@ -1,91 +0,0 @@ -# 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. - -# TODO(morganfainberg): Remove this file and extension in the "O" release as -# it is only used in support of the PKI/PKIz token providers. -import functools - -from oslo_config import cfg -import webob - -from keystone.common import controller -from keystone.common import dependency -from keystone.common import extension -from keystone.common import json_home -from keystone.common import wsgi -from keystone import exception - - -CONF = cfg.CONF -EXTENSION_DATA = { - 'name': 'OpenStack Simple Certificate API', - 'namespace': 'http://docs.openstack.org/identity/api/ext/' - 'OS-SIMPLE-CERT/v1.0', - 'alias': 'OS-SIMPLE-CERT', - 'updated': '2014-01-20T12:00:0-00:00', - 'description': 'OpenStack simple certificate retrieval extension', - 'links': [ - { - 'rel': 'describedby', - 'type': 'text/html', - 'href': 'http://developer.openstack.org/' - 'api-ref-identity-v2-ext.html', - } - ]} -extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) -extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) - -build_resource_relation = functools.partial( - json_home.build_v3_extension_resource_relation, - extension_name='OS-SIMPLE-CERT', extension_version='1.0') - - -class Routers(wsgi.RoutersBase): - - def _construct_url(self, suffix): - return "/OS-SIMPLE-CERT/%s" % suffix - - def append_v3_routers(self, mapper, routers): - controller = SimpleCert() - - self._add_resource( - mapper, controller, - path=self._construct_url('ca'), - get_action='get_ca_certificate', - rel=build_resource_relation(resource_name='ca_certificate')) - self._add_resource( - mapper, controller, - path=self._construct_url('certificates'), - get_action='list_certificates', - rel=build_resource_relation(resource_name='certificates')) - - -@dependency.requires('token_provider_api') -class SimpleCert(controller.V3Controller): - - def _get_certificate(self, name): - try: - with open(name, 'r') as f: - body = f.read() - except IOError: - raise exception.CertificateFilesUnavailable() - - # NOTE(jamielennox): We construct the webob Response ourselves here so - # that we don't pass through the JSON encoding process. - headers = [('Content-Type', 'application/x-pem-file')] - return webob.Response(body=body, headerlist=headers, status="200 OK") - - def get_ca_certificate(self, context): - return self._get_certificate(CONF.signing.ca_certs) - - def list_certificates(self, context): - return self._get_certificate(CONF.signing.certfile) diff --git a/keystone-moon/keystone/token/controllers.py b/keystone-moon/keystone/token/controllers.py deleted file mode 100644 index 6eeb23ec..00000000 --- a/keystone-moon/keystone/token/controllers.py +++ /dev/null @@ -1,529 +0,0 @@ -# Copyright 2013 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. - -import datetime -import sys - -from keystone.common import utils -from keystoneclient.common import cms -from oslo_config import cfg -from oslo_log import log -from oslo_serialization import jsonutils -from oslo_utils import timeutils -import six - -from keystone.common import controller -from keystone.common import dependency -from keystone.common import wsgi -from keystone import exception -from keystone.i18n import _ -from keystone.models import token_model -from keystone.token import provider - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - - -class ExternalAuthNotApplicable(Exception): - """External authentication is not applicable.""" - - pass - - -@dependency.requires('assignment_api', 'catalog_api', 'identity_api', - 'resource_api', 'role_api', 'token_provider_api', - 'trust_api') -class Auth(controller.V2Controller): - - @controller.v2_deprecated - def ca_cert(self, context, auth=None): - with open(CONF.signing.ca_certs, 'r') as ca_file: - data = ca_file.read() - return data - - @controller.v2_deprecated - def signing_cert(self, context, auth=None): - with open(CONF.signing.certfile, 'r') as cert_file: - data = cert_file.read() - return data - - @controller.v2_auth_deprecated - def authenticate(self, context, auth=None): - """Authenticate credentials and return a token. - - Accept auth as a dict that looks like:: - - { - "auth":{ - "passwordCredentials":{ - "username":"test_user", - "password":"mypass" - }, - "tenantName":"customer-x" - } - } - - In this case, tenant is optional, if not provided the token will be - considered "unscoped" and can later be used to get a scoped token. - - Alternatively, this call accepts auth with only a token and tenant - that will return a token that is scoped to that tenant. - """ - if auth is None: - raise exception.ValidationError(attribute='auth', - target='request body') - - if "token" in auth: - # Try to authenticate using a token - auth_info = self._authenticate_token( - context, auth) - else: - # Try external authentication - try: - auth_info = self._authenticate_external( - context, auth) - except ExternalAuthNotApplicable: - # Try local authentication - auth_info = self._authenticate_local( - context, auth) - - user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id = auth_info - # Validate that the auth info is valid and nothing is disabled - try: - self.identity_api.assert_user_enabled( - user_id=user_ref['id'], user=user_ref) - if tenant_ref: - self.resource_api.assert_project_enabled( - project_id=tenant_ref['id'], project=tenant_ref) - except AssertionError as e: - six.reraise(exception.Unauthorized, exception.Unauthorized(e), - sys.exc_info()[2]) - # NOTE(morganfainberg): Make sure the data is in correct form since it - # might be consumed external to Keystone and this is a v2.0 controller. - # The user_ref is encoded into the auth_token_data which is returned as - # part of the token data. The token provider doesn't care about the - # format. - user_ref = self.v3_to_v2_user(user_ref) - if tenant_ref: - tenant_ref = self.v3_to_v2_project(tenant_ref) - - auth_token_data = self._get_auth_token_data(user_ref, - tenant_ref, - metadata_ref, - expiry, - audit_id) - - if tenant_ref: - catalog_ref = self.catalog_api.get_catalog( - user_ref['id'], tenant_ref['id']) - else: - catalog_ref = {} - - auth_token_data['id'] = 'placeholder' - if bind: - auth_token_data['bind'] = bind - - roles_ref = [] - for role_id in metadata_ref.get('roles', []): - role_ref = self.role_api.get_role(role_id) - roles_ref.append(dict(name=role_ref['name'])) - - (token_id, token_data) = self.token_provider_api.issue_v2_token( - auth_token_data, roles_ref=roles_ref, catalog_ref=catalog_ref) - - # NOTE(wanghong): We consume a trust use only when we are using trusts - # and have successfully issued a token. - if CONF.trust.enabled and 'trust_id' in auth: - self.trust_api.consume_use(auth['trust_id']) - - return token_data - - def _restrict_scope(self, token_model_ref): - # A trust token cannot be used to get another token - if token_model_ref.trust_scoped: - raise exception.Forbidden() - if not CONF.token.allow_rescope_scoped_token: - # Do not allow conversion from scoped tokens. - if token_model_ref.project_scoped or token_model_ref.domain_scoped: - raise exception.Forbidden(action=_("rescope a scoped token")) - - def _authenticate_token(self, context, auth): - """Try to authenticate using an already existing token. - - Returns auth_token_data, (user_ref, tenant_ref, metadata_ref) - """ - if 'token' not in auth: - raise exception.ValidationError( - attribute='token', target='auth') - - if "id" not in auth['token']: - raise exception.ValidationError( - attribute="id", target="token") - - old_token = auth['token']['id'] - if len(old_token) > CONF.max_token_size: - raise exception.ValidationSizeError(attribute='token', - size=CONF.max_token_size) - - try: - token_model_ref = token_model.KeystoneToken( - token_id=old_token, - token_data=self.token_provider_api.validate_v2_token(old_token) - ) - except exception.NotFound as e: - raise exception.Unauthorized(e) - - wsgi.validate_token_bind(context, token_model_ref) - - self._restrict_scope(token_model_ref) - user_id = token_model_ref.user_id - tenant_id = self._get_project_id_from_auth(auth) - - if not CONF.trust.enabled and 'trust_id' in auth: - raise exception.Forbidden('Trusts are disabled.') - elif CONF.trust.enabled and 'trust_id' in auth: - try: - trust_ref = self.trust_api.get_trust(auth['trust_id']) - except exception.TrustNotFound: - raise exception.Forbidden() - if user_id != trust_ref['trustee_user_id']: - raise exception.Forbidden() - if (trust_ref['project_id'] and - tenant_id != trust_ref['project_id']): - raise exception.Forbidden() - if ('expires' in trust_ref) and (trust_ref['expires']): - expiry = trust_ref['expires'] - if expiry < timeutils.parse_isotime(utils.isotime()): - raise exception.Forbidden() - user_id = trust_ref['trustor_user_id'] - trustor_user_ref = self.identity_api.get_user( - trust_ref['trustor_user_id']) - if not trustor_user_ref['enabled']: - raise exception.Forbidden() - trustee_user_ref = self.identity_api.get_user( - trust_ref['trustee_user_id']) - if not trustee_user_ref['enabled']: - raise exception.Forbidden() - - if trust_ref['impersonation'] is True: - current_user_ref = trustor_user_ref - else: - current_user_ref = trustee_user_ref - - else: - current_user_ref = self.identity_api.get_user(user_id) - - metadata_ref = {} - tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref( - user_id, tenant_id) - - expiry = token_model_ref.expires - if CONF.trust.enabled and 'trust_id' in auth: - trust_id = auth['trust_id'] - trust_roles = [] - for role in trust_ref['roles']: - if 'roles' not in metadata_ref: - raise exception.Forbidden() - if role['id'] in metadata_ref['roles']: - trust_roles.append(role['id']) - else: - raise exception.Forbidden() - if 'expiry' in trust_ref and trust_ref['expiry']: - trust_expiry = timeutils.parse_isotime(trust_ref['expiry']) - if trust_expiry < expiry: - expiry = trust_expiry - metadata_ref['roles'] = trust_roles - metadata_ref['trustee_user_id'] = trust_ref['trustee_user_id'] - metadata_ref['trust_id'] = trust_id - - bind = token_model_ref.bind - audit_id = token_model_ref.audit_chain_id - - return (current_user_ref, tenant_ref, metadata_ref, expiry, bind, - audit_id) - - def _authenticate_local(self, context, auth): - """Try to authenticate against the identity backend. - - Returns auth_token_data, (user_ref, tenant_ref, metadata_ref) - """ - if 'passwordCredentials' not in auth: - raise exception.ValidationError( - attribute='passwordCredentials', target='auth') - - if "password" not in auth['passwordCredentials']: - raise exception.ValidationError( - attribute='password', target='passwordCredentials') - - password = auth['passwordCredentials']['password'] - if password and len(password) > CONF.identity.max_password_length: - raise exception.ValidationSizeError( - attribute='password', size=CONF.identity.max_password_length) - - if (not auth['passwordCredentials'].get("userId") and - not auth['passwordCredentials'].get("username")): - raise exception.ValidationError( - attribute='username or userId', - target='passwordCredentials') - - user_id = auth['passwordCredentials'].get('userId') - if user_id and len(user_id) > CONF.max_param_size: - raise exception.ValidationSizeError(attribute='userId', - size=CONF.max_param_size) - - username = auth['passwordCredentials'].get('username', '') - - if username: - if len(username) > CONF.max_param_size: - raise exception.ValidationSizeError(attribute='username', - size=CONF.max_param_size) - try: - user_ref = self.identity_api.get_user_by_name( - username, CONF.identity.default_domain_id) - user_id = user_ref['id'] - except exception.UserNotFound as e: - raise exception.Unauthorized(e) - - try: - user_ref = self.identity_api.authenticate( - context, - user_id=user_id, - password=password) - except AssertionError as e: - raise exception.Unauthorized(e.args[0]) - - metadata_ref = {} - tenant_id = self._get_project_id_from_auth(auth) - tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref( - user_id, tenant_id) - - expiry = provider.default_expire_time() - bind = None - audit_id = None - return (user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id) - - def _authenticate_external(self, context, auth): - """Try to authenticate an external user via REMOTE_USER variable. - - Returns auth_token_data, (user_ref, tenant_ref, metadata_ref) - """ - environment = context.get('environment', {}) - if not environment.get('REMOTE_USER'): - raise ExternalAuthNotApplicable() - - username = environment['REMOTE_USER'] - try: - user_ref = self.identity_api.get_user_by_name( - username, CONF.identity.default_domain_id) - user_id = user_ref['id'] - except exception.UserNotFound as e: - raise exception.Unauthorized(e) - - metadata_ref = {} - tenant_id = self._get_project_id_from_auth(auth) - tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref( - user_id, tenant_id) - - expiry = provider.default_expire_time() - bind = None - if ('kerberos' in CONF.token.bind and - environment.get('AUTH_TYPE', '').lower() == 'negotiate'): - bind = {'kerberos': username} - audit_id = None - - return (user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id) - - def _get_auth_token_data(self, user, tenant, metadata, expiry, audit_id): - return dict(user=user, - tenant=tenant, - metadata=metadata, - expires=expiry, - parent_audit_id=audit_id) - - def _get_project_id_from_auth(self, auth): - """Extract tenant information from auth dict. - - Returns a valid tenant_id if it exists, or None if not specified. - """ - tenant_id = auth.get('tenantId') - if tenant_id and len(tenant_id) > CONF.max_param_size: - raise exception.ValidationSizeError(attribute='tenantId', - size=CONF.max_param_size) - - tenant_name = auth.get('tenantName') - if tenant_name and len(tenant_name) > CONF.max_param_size: - raise exception.ValidationSizeError(attribute='tenantName', - size=CONF.max_param_size) - - if tenant_name: - if (CONF.resource.project_name_url_safe == 'strict' and - utils.is_not_url_safe(tenant_name)): - msg = _('Tenant name cannot contain reserved characters.') - raise exception.Unauthorized(message=msg) - try: - tenant_ref = self.resource_api.get_project_by_name( - tenant_name, CONF.identity.default_domain_id) - tenant_id = tenant_ref['id'] - except exception.ProjectNotFound as e: - raise exception.Unauthorized(e) - return tenant_id - - def _get_project_roles_and_ref(self, user_id, tenant_id): - """Returns the project roles for this user, and the project ref.""" - tenant_ref = None - role_list = [] - if tenant_id: - try: - tenant_ref = self.resource_api.get_project(tenant_id) - role_list = self.assignment_api.get_roles_for_user_and_project( - user_id, tenant_id) - except exception.ProjectNotFound: - msg = _('Project ID not found: %(t_id)s') % {'t_id': tenant_id} - raise exception.Unauthorized(msg) - - if not role_list: - msg = _('User %(u_id)s is unauthorized for tenant %(t_id)s') - msg = msg % {'u_id': user_id, 't_id': tenant_id} - LOG.warning(msg) - raise exception.Unauthorized(msg) - - return (tenant_ref, role_list) - - def _get_token_ref(self, token_id, belongs_to=None): - """Returns a token if a valid one exists. - - Optionally, limited to a token owned by a specific tenant. - - """ - token_ref = token_model.KeystoneToken( - token_id=token_id, - token_data=self.token_provider_api.validate_token(token_id)) - if belongs_to: - if not token_ref.project_scoped: - raise exception.Unauthorized( - _('Token does not belong to specified tenant.')) - if token_ref.project_id != belongs_to: - raise exception.Unauthorized( - _('Token does not belong to specified tenant.')) - return token_ref - - @controller.v2_deprecated - @controller.protected() - def validate_token_head(self, context, token_id): - """Check that a token is valid. - - Optionally, also ensure that it is owned by a specific tenant. - - Identical to ``validate_token``, except does not return a response. - - The code in ``keystone.common.wsgi.render_response`` will remove - the content body. - - """ - belongs_to = context['query_string'].get('belongsTo') - return self.token_provider_api.validate_v2_token(token_id, belongs_to) - - @controller.v2_deprecated - @controller.protected() - def validate_token(self, context, token_id): - """Check that a token is valid. - - Optionally, also ensure that it is owned by a specific tenant. - - Returns metadata about the token along any associated roles. - - """ - belongs_to = context['query_string'].get('belongsTo') - # TODO(ayoung) validate against revocation API - return self.token_provider_api.validate_v2_token(token_id, belongs_to) - - @controller.v2_deprecated - def delete_token(self, context, token_id): - """Delete a token, effectively invalidating it for authz.""" - # TODO(termie): this stuff should probably be moved to middleware - self.assert_admin(context) - self.token_provider_api.revoke_token(token_id) - - @controller.v2_deprecated - @controller.protected() - def revocation_list(self, context, auth=None): - if not CONF.token.revoke_by_id: - raise exception.Gone() - tokens = self.token_provider_api.list_revoked_tokens() - - for t in tokens: - expires = t['expires'] - if expires and isinstance(expires, datetime.datetime): - t['expires'] = utils.isotime(expires) - data = {'revoked': tokens} - json_data = jsonutils.dumps(data) - signed_text = cms.cms_sign_text(json_data, - CONF.signing.certfile, - CONF.signing.keyfile) - - return {'signed': signed_text} - - @controller.v2_deprecated - def endpoints(self, context, token_id): - """Return a list of endpoints available to the token.""" - self.assert_admin(context) - - token_ref = self._get_token_ref(token_id) - - catalog_ref = None - if token_ref.project_id: - catalog_ref = self.catalog_api.get_catalog( - token_ref.user_id, - token_ref.project_id) - - return Auth.format_endpoint_list(catalog_ref) - - @classmethod - def format_endpoint_list(cls, catalog_ref): - """Formats a list of endpoints according to Identity API v2. - - The v2.0 API wants an endpoint list to look like:: - - { - 'endpoints': [ - { - 'id': $endpoint_id, - 'name': $SERVICE[name], - 'type': $SERVICE, - 'tenantId': $tenant_id, - 'region': $REGION, - } - ], - 'endpoints_links': [], - } - - """ - if not catalog_ref: - return {} - - endpoints = [] - for region_name, region_ref in catalog_ref.items(): - for service_type, service_ref in region_ref.items(): - endpoints.append({ - 'id': service_ref.get('id'), - 'name': service_ref.get('name'), - 'type': service_type, - 'region': region_name, - 'publicURL': service_ref.get('publicURL'), - 'internalURL': service_ref.get('internalURL'), - 'adminURL': service_ref.get('adminURL'), - }) - - return {'endpoints': endpoints, 'endpoints_links': []} diff --git a/keystone-moon/keystone/token/persistence/__init__.py b/keystone-moon/keystone/token/persistence/__init__.py deleted file mode 100644 index 9d8e17f2..00000000 --- a/keystone-moon/keystone/token/persistence/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - -from keystone.token.persistence.core import * # noqa - - -__all__ = ('Manager', 'Driver') diff --git a/keystone-moon/keystone/token/persistence/backends/__init__.py b/keystone-moon/keystone/token/persistence/backends/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/keystone-moon/keystone/token/persistence/backends/__init__.py +++ /dev/null diff --git a/keystone-moon/keystone/token/persistence/backends/kvs.py b/keystone-moon/keystone/token/persistence/backends/kvs.py deleted file mode 100644 index 3620db58..00000000 --- a/keystone-moon/keystone/token/persistence/backends/kvs.py +++ /dev/null @@ -1,367 +0,0 @@ -# Copyright 2013 Metacloud, Inc. -# 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. - -from __future__ import absolute_import -import copy - -from oslo_config import cfg -from oslo_log import log -from oslo_utils import timeutils -import six - -from keystone.common import kvs -from keystone.common import utils -from keystone import exception -from keystone.i18n import _, _LE, _LW -from keystone import token -from keystone.token import provider - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - - -class Token(token.persistence.TokenDriverV8): - """KeyValueStore backend for tokens. - - This is the base implementation for any/all key-value-stores (e.g. - memcached) for the Token backend. It is recommended to only use the base - in-memory implementation for testing purposes. - """ - - revocation_key = 'revocation-list' - kvs_backend = 'openstack.kvs.Memory' - - def __init__(self, backing_store=None, **kwargs): - super(Token, self).__init__() - self._store = kvs.get_key_value_store('token-driver') - if backing_store is not None: - self.kvs_backend = backing_store - if not self._store.is_configured: - # Do not re-configure the backend if the store has been initialized - self._store.configure(backing_store=self.kvs_backend, **kwargs) - if self.__class__ == Token: - # NOTE(morganfainberg): Only warn if the base KVS implementation - # is instantiated. - LOG.warning(_LW('It is recommended to only use the base ' - 'key-value-store implementation for the token ' - 'driver for testing purposes. Please use ' - "'memcache' or 'sql' instead.")) - - def _prefix_token_id(self, token_id): - return 'token-%s' % token_id.encode('utf-8') - - def _prefix_user_id(self, user_id): - return 'usertokens-%s' % user_id.encode('utf-8') - - def _get_key_or_default(self, key, default=None): - try: - return self._store.get(key) - except exception.NotFound: - return default - - def _get_key(self, key): - return self._store.get(key) - - def _set_key(self, key, value, lock=None): - self._store.set(key, value, lock) - - def _delete_key(self, key): - return self._store.delete(key) - - def get_token(self, token_id): - ptk = self._prefix_token_id(token_id) - try: - token_ref = self._get_key(ptk) - except exception.NotFound: - raise exception.TokenNotFound(token_id=token_id) - - return token_ref - - def create_token(self, token_id, data): - """Create a token by id and data. - - It is assumed the caller has performed data validation on the "data" - parameter. - """ - data_copy = copy.deepcopy(data) - ptk = self._prefix_token_id(token_id) - if not data_copy.get('expires'): - data_copy['expires'] = provider.default_expire_time() - if not data_copy.get('user_id'): - data_copy['user_id'] = data_copy['user']['id'] - - # NOTE(morganfainberg): for ease of manipulating the data without - # concern about the backend, always store the value(s) in the - # index as the isotime (string) version so this is where the string is - # built. - expires_str = utils.isotime(data_copy['expires'], subsecond=True) - - self._set_key(ptk, data_copy) - user_id = data['user']['id'] - user_key = self._prefix_user_id(user_id) - self._update_user_token_list(user_key, token_id, expires_str) - if CONF.trust.enabled and data.get('trust_id'): - # NOTE(morganfainberg): If trusts are enabled and this is a trust - # scoped token, we add the token to the trustee list as well. This - # allows password changes of the trustee to also expire the token. - # There is no harm in placing the token in multiple lists, as - # _list_tokens is smart enough to handle almost any case of - # valid/invalid/expired for a given token. - token_data = data_copy['token_data'] - if data_copy['token_version'] == token.provider.V2: - trustee_user_id = token_data['access']['trust'][ - 'trustee_user_id'] - elif data_copy['token_version'] == token.provider.V3: - trustee_user_id = token_data['OS-TRUST:trust'][ - 'trustee_user_id'] - else: - raise exception.UnsupportedTokenVersionException( - _('Unknown token version %s') % - data_copy.get('token_version')) - - trustee_key = self._prefix_user_id(trustee_user_id) - self._update_user_token_list(trustee_key, token_id, expires_str) - - return data_copy - - def _get_user_token_list_with_expiry(self, user_key): - """Return user token list with token expiry. - - :return: the tuples in the format (token_id, token_expiry) - :rtype: list - """ - return self._get_key_or_default(user_key, default=[]) - - def _get_user_token_list(self, user_key): - """Return a list of token_ids for the user_key.""" - token_list = self._get_user_token_list_with_expiry(user_key) - # Each element is a tuple of (token_id, token_expiry). Most code does - # not care about the expiry, it is stripped out and only a - # list of token_ids are returned. - return [t[0] for t in token_list] - - def _update_user_token_list(self, user_key, token_id, expires_isotime_str): - current_time = self._get_current_time() - revoked_token_list = set([t['id'] for t in - self.list_revoked_tokens()]) - - with self._store.get_lock(user_key) as lock: - filtered_list = [] - token_list = self._get_user_token_list_with_expiry(user_key) - for item in token_list: - try: - item_id, expires = self._format_token_index_item(item) - except (ValueError, TypeError): - # NOTE(morganfainberg): Skip on expected errors - # possibilities from the `_format_token_index_item` method. - continue - - if expires < current_time: - LOG.debug(('Token `%(token_id)s` is expired, removing ' - 'from `%(user_key)s`.'), - {'token_id': item_id, 'user_key': user_key}) - continue - - if item_id in revoked_token_list: - # NOTE(morganfainberg): If the token has been revoked, it - # can safely be removed from this list. This helps to keep - # the user_token_list as reasonably small as possible. - LOG.debug(('Token `%(token_id)s` is revoked, removing ' - 'from `%(user_key)s`.'), - {'token_id': item_id, 'user_key': user_key}) - continue - filtered_list.append(item) - filtered_list.append((token_id, expires_isotime_str)) - self._set_key(user_key, filtered_list, lock) - return filtered_list - - def _get_current_time(self): - return timeutils.normalize_time(timeutils.utcnow()) - - def _add_to_revocation_list(self, data, lock): - filtered_list = [] - revoked_token_data = {} - - current_time = self._get_current_time() - expires = data['expires'] - - if isinstance(expires, six.string_types): - expires = timeutils.parse_isotime(expires) - - expires = timeutils.normalize_time(expires) - - if expires < current_time: - LOG.warning(_LW('Token `%s` is expired, not adding to the ' - 'revocation list.'), data['id']) - return - - revoked_token_data['expires'] = utils.isotime(expires, - subsecond=True) - revoked_token_data['id'] = data['id'] - - token_data = data['token_data'] - if 'access' in token_data: - # It's a v2 token. - audit_ids = token_data['access']['token']['audit_ids'] - else: - # It's a v3 token. - audit_ids = token_data['token']['audit_ids'] - revoked_token_data['audit_id'] = audit_ids[0] - - token_list = self._get_key_or_default(self.revocation_key, default=[]) - if not isinstance(token_list, list): - # NOTE(morganfainberg): In the case that the revocation list is not - # in a format we understand, reinitialize it. This is an attempt to - # not allow the revocation list to be completely broken if - # somehow the key is changed outside of keystone (e.g. memcache - # that is shared by multiple applications). Logging occurs at error - # level so that the cloud administrators have some awareness that - # the revocation_list needed to be cleared out. In all, this should - # be recoverable. Keystone cannot control external applications - # from changing a key in some backends, however, it is possible to - # gracefully handle and notify of this event. - LOG.error(_LE('Reinitializing revocation list due to error ' - 'in loading revocation list from backend. ' - 'Expected `list` type got `%(type)s`. Old ' - 'revocation list data: %(list)r'), - {'type': type(token_list), 'list': token_list}) - token_list = [] - - # NOTE(morganfainberg): on revocation, cleanup the expired entries, try - # to keep the list of tokens revoked at the minimum. - for token_data in token_list: - try: - expires_at = timeutils.normalize_time( - timeutils.parse_isotime(token_data['expires'])) - except ValueError: - LOG.warning(_LW('Removing `%s` from revocation list due to ' - 'invalid expires data in revocation list.'), - token_data.get('id', 'INVALID_TOKEN_DATA')) - continue - if expires_at > current_time: - filtered_list.append(token_data) - filtered_list.append(revoked_token_data) - self._set_key(self.revocation_key, filtered_list, lock) - - def delete_token(self, token_id): - # Test for existence - with self._store.get_lock(self.revocation_key) as lock: - data = self.get_token(token_id) - ptk = self._prefix_token_id(token_id) - result = self._delete_key(ptk) - self._add_to_revocation_list(data, lock) - return result - - def delete_tokens(self, user_id, tenant_id=None, trust_id=None, - consumer_id=None): - return super(Token, self).delete_tokens( - user_id=user_id, - tenant_id=tenant_id, - trust_id=trust_id, - consumer_id=consumer_id, - ) - - def _format_token_index_item(self, item): - try: - token_id, expires = item - except (TypeError, ValueError): - LOG.debug(('Invalid token entry expected tuple of ' - '`(<token_id>, <expires>)` got: `%(item)r`'), - dict(item=item)) - raise - - try: - expires = timeutils.normalize_time( - timeutils.parse_isotime(expires)) - except ValueError: - LOG.debug(('Invalid expires time on token `%(token_id)s`:' - ' %(expires)r'), - dict(token_id=token_id, expires=expires)) - raise - return token_id, expires - - def _token_match_tenant(self, token_ref, tenant_id): - if token_ref.get('tenant'): - return token_ref['tenant'].get('id') == tenant_id - return False - - def _token_match_trust(self, token_ref, trust_id): - if not token_ref.get('trust_id'): - return False - return token_ref['trust_id'] == trust_id - - def _token_match_consumer(self, token_ref, consumer_id): - try: - oauth = token_ref['token_data']['token']['OS-OAUTH1'] - return oauth.get('consumer_id') == consumer_id - except KeyError: - return False - - def _list_tokens(self, user_id, tenant_id=None, trust_id=None, - consumer_id=None): - # This function is used to generate the list of tokens that should be - # revoked when revoking by token identifiers. This approach will be - # deprecated soon, probably in the Juno release. Setting revoke_by_id - # to False indicates that this kind of recording should not be - # performed. In order to test the revocation events, tokens shouldn't - # be deleted from the backends. This check ensures that tokens are - # still recorded. - if not CONF.token.revoke_by_id: - return [] - tokens = [] - user_key = self._prefix_user_id(user_id) - token_list = self._get_user_token_list_with_expiry(user_key) - current_time = self._get_current_time() - for item in token_list: - try: - token_id, expires = self._format_token_index_item(item) - except (TypeError, ValueError): - # NOTE(morganfainberg): Skip on expected error possibilities - # from the `_format_token_index_item` method. - continue - - if expires < current_time: - continue - - try: - token_ref = self.get_token(token_id) - except exception.TokenNotFound: - # NOTE(morganfainberg): Token doesn't exist, skip it. - continue - if token_ref: - if tenant_id is not None: - if not self._token_match_tenant(token_ref, tenant_id): - continue - if trust_id is not None: - if not self._token_match_trust(token_ref, trust_id): - continue - if consumer_id is not None: - if not self._token_match_consumer(token_ref, consumer_id): - continue - - tokens.append(token_id) - return tokens - - def list_revoked_tokens(self): - revoked_token_list = self._get_key_or_default(self.revocation_key, - default=[]) - if isinstance(revoked_token_list, list): - return revoked_token_list - return [] - - def flush_expired_tokens(self): - """Archive or delete tokens that have expired.""" - raise exception.NotImplemented() diff --git a/keystone-moon/keystone/token/persistence/backends/memcache.py b/keystone-moon/keystone/token/persistence/backends/memcache.py deleted file mode 100644 index e6b0fcab..00000000 --- a/keystone-moon/keystone/token/persistence/backends/memcache.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2013 Metacloud, Inc. -# 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. - -from oslo_config import cfg -from oslo_log import versionutils - -from keystone.token.persistence.backends import kvs - - -CONF = cfg.CONF - - -class Token(kvs.Token): - kvs_backend = 'openstack.kvs.Memcached' - memcached_backend = 'memcached' - - @versionutils.deprecated( - what='Memcache Token Persistence Driver', - as_of=versionutils.deprecated.MITAKA, - in_favor_of='fernet token driver (no-persistence)', - remove_in=0) - def __init__(self, *args, **kwargs): - kwargs['memcached_backend'] = self.memcached_backend - kwargs['no_expiry_keys'] = [self.revocation_key] - kwargs['memcached_expire_time'] = CONF.token.expiration - kwargs['url'] = CONF.memcache.servers - super(Token, self).__init__(*args, **kwargs) diff --git a/keystone-moon/keystone/token/persistence/backends/memcache_pool.py b/keystone-moon/keystone/token/persistence/backends/memcache_pool.py deleted file mode 100644 index 39a5ca65..00000000 --- a/keystone-moon/keystone/token/persistence/backends/memcache_pool.py +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -from oslo_config import cfg -from oslo_log import versionutils - -from keystone.token.persistence.backends import memcache - - -CONF = cfg.CONF - - -class Token(memcache.Token): - memcached_backend = 'pooled_memcached' - - @versionutils.deprecated( - what='Memcache Pool Token Persistence Driver', - as_of=versionutils.deprecated.MITAKA, - in_favor_of='fernet token driver (no-persistence)', - remove_in=0) - def __init__(self, *args, **kwargs): - for arg in ('dead_retry', 'socket_timeout', 'pool_maxsize', - 'pool_unused_timeout', 'pool_connection_get_timeout'): - kwargs[arg] = getattr(CONF.memcache, arg) - super(Token, self).__init__(*args, **kwargs) diff --git a/keystone-moon/keystone/token/persistence/backends/sql.py b/keystone-moon/keystone/token/persistence/backends/sql.py deleted file mode 100644 index 4b3439a1..00000000 --- a/keystone-moon/keystone/token/persistence/backends/sql.py +++ /dev/null @@ -1,286 +0,0 @@ -# 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. - -import copy -import functools - -from oslo_config import cfg -from oslo_log import log -from oslo_utils import timeutils - -from keystone.common import sql -from keystone import exception -from keystone.i18n import _LI -from keystone import token -from keystone.token import provider - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - - -class TokenModel(sql.ModelBase, sql.DictBase): - __tablename__ = 'token' - attributes = ['id', 'expires', 'user_id', 'trust_id'] - id = sql.Column(sql.String(64), primary_key=True) - expires = sql.Column(sql.DateTime(), default=None) - extra = sql.Column(sql.JsonBlob()) - valid = sql.Column(sql.Boolean(), default=True, nullable=False) - user_id = sql.Column(sql.String(64)) - trust_id = sql.Column(sql.String(64)) - __table_args__ = ( - sql.Index('ix_token_expires', 'expires'), - sql.Index('ix_token_expires_valid', 'expires', 'valid'), - sql.Index('ix_token_user_id', 'user_id'), - sql.Index('ix_token_trust_id', 'trust_id') - ) - - -def _expiry_range_batched(session, upper_bound_func, batch_size): - """Returns the stop point of the next batch for expiration. - - Return the timestamp of the next token that is `batch_size` rows from - being the oldest expired token. - """ - # This expiry strategy splits the tokens into roughly equal sized batches - # to be deleted. It does this by finding the timestamp of a token - # `batch_size` rows from the oldest token and yielding that to the caller. - # It's expected that the caller will then delete all rows with a timestamp - # equal to or older than the one yielded. This may delete slightly more - # tokens than the batch_size, but that should be ok in almost all cases. - LOG.debug('Token expiration batch size: %d', batch_size) - query = session.query(TokenModel.expires) - query = query.filter(TokenModel.expires < upper_bound_func()) - query = query.order_by(TokenModel.expires) - query = query.offset(batch_size - 1) - query = query.limit(1) - while True: - try: - next_expiration = query.one()[0] - except sql.NotFound: - # There are less than `batch_size` rows remaining, so fall - # through to the normal delete - break - yield next_expiration - yield upper_bound_func() - - -def _expiry_range_all(session, upper_bound_func): - """Expires all tokens in one pass.""" - yield upper_bound_func() - - -class Token(token.persistence.TokenDriverV8): - # Public interface - def get_token(self, token_id): - if token_id is None: - raise exception.TokenNotFound(token_id=token_id) - with sql.session_for_read() as session: - token_ref = session.query(TokenModel).get(token_id) - if not token_ref or not token_ref.valid: - raise exception.TokenNotFound(token_id=token_id) - return token_ref.to_dict() - - def create_token(self, token_id, data): - data_copy = copy.deepcopy(data) - if not data_copy.get('expires'): - data_copy['expires'] = provider.default_expire_time() - if not data_copy.get('user_id'): - data_copy['user_id'] = data_copy['user']['id'] - - token_ref = TokenModel.from_dict(data_copy) - token_ref.valid = True - with sql.session_for_write() as session: - session.add(token_ref) - return token_ref.to_dict() - - def delete_token(self, token_id): - with sql.session_for_write() as session: - token_ref = session.query(TokenModel).get(token_id) - if not token_ref or not token_ref.valid: - raise exception.TokenNotFound(token_id=token_id) - token_ref.valid = False - - def delete_tokens(self, user_id, tenant_id=None, trust_id=None, - consumer_id=None): - """Deletes all tokens in one session - - The user_id will be ignored if the trust_id is specified. user_id - will always be specified. - If using a trust, the token's user_id is set to the trustee's user ID - or the trustor's user ID, so will use trust_id to query the tokens. - - """ - token_list = [] - with sql.session_for_write() as session: - now = timeutils.utcnow() - query = session.query(TokenModel) - query = query.filter_by(valid=True) - query = query.filter(TokenModel.expires > now) - if trust_id: - query = query.filter(TokenModel.trust_id == trust_id) - else: - query = query.filter(TokenModel.user_id == user_id) - - for token_ref in query.all(): - if tenant_id: - token_ref_dict = token_ref.to_dict() - if not self._tenant_matches(tenant_id, token_ref_dict): - continue - if consumer_id: - token_ref_dict = token_ref.to_dict() - if not self._consumer_matches(consumer_id, token_ref_dict): - continue - - token_ref.valid = False - token_list.append(token_ref.id) - - return token_list - - def _tenant_matches(self, tenant_id, token_ref_dict): - return ((tenant_id is None) or - (token_ref_dict.get('tenant') and - token_ref_dict['tenant'].get('id') == tenant_id)) - - def _consumer_matches(self, consumer_id, ref): - if consumer_id is None: - return True - else: - try: - oauth = ref['token_data']['token'].get('OS-OAUTH1', {}) - return oauth and oauth['consumer_id'] == consumer_id - except KeyError: - return False - - def _list_tokens_for_trust(self, trust_id): - with sql.session_for_read() as session: - tokens = [] - now = timeutils.utcnow() - query = session.query(TokenModel) - query = query.filter(TokenModel.expires > now) - query = query.filter(TokenModel.trust_id == trust_id) - - token_references = query.filter_by(valid=True) - for token_ref in token_references: - token_ref_dict = token_ref.to_dict() - tokens.append(token_ref_dict['id']) - return tokens - - def _list_tokens_for_user(self, user_id, tenant_id=None): - with sql.session_for_read() as session: - tokens = [] - now = timeutils.utcnow() - query = session.query(TokenModel) - query = query.filter(TokenModel.expires > now) - query = query.filter(TokenModel.user_id == user_id) - - token_references = query.filter_by(valid=True) - for token_ref in token_references: - token_ref_dict = token_ref.to_dict() - if self._tenant_matches(tenant_id, token_ref_dict): - tokens.append(token_ref['id']) - return tokens - - def _list_tokens_for_consumer(self, user_id, consumer_id): - tokens = [] - with sql.session_for_write() as session: - now = timeutils.utcnow() - query = session.query(TokenModel) - query = query.filter(TokenModel.expires > now) - query = query.filter(TokenModel.user_id == user_id) - token_references = query.filter_by(valid=True) - - for token_ref in token_references: - token_ref_dict = token_ref.to_dict() - if self._consumer_matches(consumer_id, token_ref_dict): - tokens.append(token_ref_dict['id']) - return tokens - - def _list_tokens(self, user_id, tenant_id=None, trust_id=None, - consumer_id=None): - if not CONF.token.revoke_by_id: - return [] - if trust_id: - return self._list_tokens_for_trust(trust_id) - if consumer_id: - return self._list_tokens_for_consumer(user_id, consumer_id) - else: - return self._list_tokens_for_user(user_id, tenant_id) - - def list_revoked_tokens(self): - with sql.session_for_read() as session: - tokens = [] - now = timeutils.utcnow() - query = session.query(TokenModel.id, TokenModel.expires, - TokenModel.extra) - query = query.filter(TokenModel.expires > now) - token_references = query.filter_by(valid=False) - for token_ref in token_references: - token_data = token_ref[2]['token_data'] - if 'access' in token_data: - # It's a v2 token. - audit_ids = token_data['access']['token']['audit_ids'] - else: - # It's a v3 token. - audit_ids = token_data['token']['audit_ids'] - - record = { - 'id': token_ref[0], - 'expires': token_ref[1], - 'audit_id': audit_ids[0], - } - tokens.append(record) - return tokens - - def _expiry_range_strategy(self, dialect): - """Choose a token range expiration strategy - - Based on the DB dialect, select an expiry range callable that is - appropriate. - """ - # DB2 and MySQL can both benefit from a batched strategy. On DB2 the - # transaction log can fill up and on MySQL w/Galera, large - # transactions can exceed the maximum write set size. - if dialect == 'ibm_db_sa': - # Limit of 100 is known to not fill a transaction log - # of default maximum size while not significantly - # impacting the performance of large token purges on - # systems where the maximum transaction log size has - # been increased beyond the default. - return functools.partial(_expiry_range_batched, - batch_size=100) - elif dialect == 'mysql': - # We want somewhat more than 100, since Galera replication delay is - # at least RTT*2. This can be a significant amount of time if - # doing replication across a WAN. - return functools.partial(_expiry_range_batched, - batch_size=1000) - return _expiry_range_all - - def flush_expired_tokens(self): - with sql.session_for_write() as session: - dialect = session.bind.dialect.name - expiry_range_func = self._expiry_range_strategy(dialect) - query = session.query(TokenModel.expires) - total_removed = 0 - upper_bound_func = timeutils.utcnow - for expiry_time in expiry_range_func(session, upper_bound_func): - delete_query = query.filter(TokenModel.expires <= - expiry_time) - row_count = delete_query.delete(synchronize_session=False) - total_removed += row_count - LOG.debug('Removed %d total expired tokens', total_removed) - - session.flush() - LOG.info(_LI('Total expired tokens removed: %d'), total_removed) diff --git a/keystone-moon/keystone/token/persistence/core.py b/keystone-moon/keystone/token/persistence/core.py deleted file mode 100644 index 76c3ff70..00000000 --- a/keystone-moon/keystone/token/persistence/core.py +++ /dev/null @@ -1,357 +0,0 @@ -# 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 -from keystone.token import utils - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) -MEMOIZE = cache.get_memoization_decorator(group='token') -REVOCATION_MEMOIZE = cache.get_memoization_decorator(group='token', - expiration_group='revoke') - - -@dependency.requires('assignment_api', 'identity_api', 'resource_api', - 'token_provider_api', 'trust_api') -class PersistenceManager(manager.Manager): - """Default pivot point for the Token Persistence backend. - - See :mod:`keystone.common.manager.Manager` for more details on how this - dynamically calls the backend. - - """ - - driver_namespace = 'keystone.token.persistence' - - 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): - unique_id = utils.generate_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 = utils.generate_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 = utils.generate_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.delete_tokens(user_id, tenant_id, trust_id, - consumer_id) - for token_id in token_list: - unique_id = utils.generate_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) - - -@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 TokenDriverV8(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: If the token doesn't exist. - - """ - 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: If the token doesn't exist. - - """ - 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: The tokens that have been deleted. - :raises keystone.exception.TokenNotFound: If the token doesn't exist. - - """ - 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: # nosec - # The token is already gone, good. - pass - return token_list - - @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 - - -Driver = manager.create_legacy_driver(TokenDriverV8) diff --git a/keystone-moon/keystone/token/provider.py b/keystone-moon/keystone/token/provider.py deleted file mode 100644 index 7c4166f4..00000000 --- a/keystone-moon/keystone/token/provider.py +++ /dev/null @@ -1,637 +0,0 @@ -# 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. - -"""Token provider interface.""" - -import abc -import base64 -import datetime -import sys -import uuid - -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 _, _LE -from keystone.models import token_model -from keystone import notifications -from keystone.token import persistence -from keystone.token import providers -from keystone.token import utils - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) -MEMOIZE = cache.get_memoization_decorator(group='token') - -# NOTE(morganfainberg): This is for compatibility in case someone was relying -# on the old location of the UnsupportedTokenVersionException for their code. -UnsupportedTokenVersionException = exception.UnsupportedTokenVersionException - -# supported token versions -V2 = token_model.V2 -V3 = token_model.V3 -VERSIONS = token_model.VERSIONS - - -def base64_encode(s): - """Encode a URL-safe string. - - :type s: six.text_type - :rtype: six.text_type - - """ - # urlsafe_b64encode() returns six.binary_type so need to convert to - # six.text_type, might as well do it before stripping. - return base64.urlsafe_b64encode(s).decode('utf-8').rstrip('=') - - -def random_urlsafe_str(): - """Generate a random URL-safe string. - - :rtype: six.text_type - - """ - # chop the padding (==) off the end of the encoding to save space - return base64.urlsafe_b64encode(uuid.uuid4().bytes)[:-2].decode('utf-8') - - -def random_urlsafe_str_to_bytes(s): - """Convert a string from :func:`random_urlsafe_str()` to six.binary_type. - - :type s: six.text_type - :rtype: six.binary_type - - """ - # urlsafe_b64decode() requires str, unicode isn't accepted. - s = str(s) - - # restore the padding (==) at the end of the string - return base64.urlsafe_b64decode(s + '==') - - -def default_expire_time(): - """Determine when a fresh token should expire. - - Expiration time varies based on configuration (see ``[token] expiration``). - - :returns: a naive UTC datetime.datetime object - - """ - expire_delta = datetime.timedelta(seconds=CONF.token.expiration) - return timeutils.utcnow() + expire_delta - - -def audit_info(parent_audit_id): - """Build the audit data for a token. - - If ``parent_audit_id`` is None, the list will be one element in length - containing a newly generated audit_id. - - If ``parent_audit_id`` is supplied, the list will be two elements in length - containing a newly generated audit_id and the ``parent_audit_id``. The - ``parent_audit_id`` will always be element index 1 in the resulting - list. - - :param parent_audit_id: the audit of the original token in the chain - :type parent_audit_id: str - :returns: Keystone token audit data - """ - audit_id = random_urlsafe_str() - if parent_audit_id is not None: - return [audit_id, parent_audit_id] - return [audit_id] - - -@dependency.provider('token_provider_api') -@dependency.requires('assignment_api', 'revoke_api') -class Manager(manager.Manager): - """Default pivot point for the token provider backend. - - See :mod:`keystone.common.manager.Manager` for more details on how this - dynamically calls the backend. - - """ - - driver_namespace = 'keystone.token.provider' - - V2 = V2 - V3 = V3 - VERSIONS = VERSIONS - INVALIDATE_PROJECT_TOKEN_PERSISTENCE = 'invalidate_project_tokens' - INVALIDATE_USER_TOKEN_PERSISTENCE = 'invalidate_user_tokens' - _persistence_manager = None - - def __init__(self): - super(Manager, self).__init__(CONF.token.provider) - self._register_callback_listeners() - - def _register_callback_listeners(self): - # This is used by the @dependency.provider decorator to register the - # provider (token_provider_api) manager to listen for trust deletions. - callbacks = { - notifications.ACTIONS.deleted: [ - ['OS-TRUST:trust', self._trust_deleted_event_callback], - ['user', self._delete_user_tokens_callback], - ['domain', self._delete_domain_tokens_callback], - ], - notifications.ACTIONS.disabled: [ - ['user', self._delete_user_tokens_callback], - ['domain', self._delete_domain_tokens_callback], - ['project', self._delete_project_tokens_callback], - ], - notifications.ACTIONS.internal: [ - [notifications.INVALIDATE_USER_TOKEN_PERSISTENCE, - self._delete_user_tokens_callback], - [notifications.INVALIDATE_USER_PROJECT_TOKEN_PERSISTENCE, - self._delete_user_project_tokens_callback], - [notifications.INVALIDATE_USER_OAUTH_CONSUMER_TOKENS, - self._delete_user_oauth_consumer_tokens_callback], - ] - } - - for event, cb_info in callbacks.items(): - for resource_type, callback_fns in cb_info: - notifications.register_event_callback(event, resource_type, - callback_fns) - - @property - def _needs_persistence(self): - return self.driver.needs_persistence() - - @property - def _persistence(self): - # NOTE(morganfainberg): This should not be handled via __init__ to - # avoid dependency injection oddities circular dependencies (where - # the provider manager requires the token persistence manager, which - # requires the token provider manager). - if self._persistence_manager is None: - self._persistence_manager = persistence.PersistenceManager() - return self._persistence_manager - - def _create_token(self, token_id, token_data): - try: - if isinstance(token_data['expires'], six.string_types): - token_data['expires'] = timeutils.normalize_time( - timeutils.parse_isotime(token_data['expires'])) - self._persistence.create_token(token_id, token_data) - except Exception: - exc_info = sys.exc_info() - # an identical token may have been created already. - # if so, return the token_data as it is also identical - try: - self._persistence.get_token(token_id) - except exception.TokenNotFound: - six.reraise(*exc_info) - - def validate_token(self, token_id, belongs_to=None): - unique_id = utils.generate_unique_id(token_id) - # NOTE(morganfainberg): Ensure we never use the long-form token_id - # (PKI) as part of the cache_key. - token = self._validate_token(unique_id) - self._token_belongs_to(token, belongs_to) - self._is_valid_token(token) - return token - - def check_revocation_v2(self, token): - try: - token_data = token['access'] - except KeyError: - raise exception.TokenNotFound(_('Failed to validate token')) - - token_values = self.revoke_api.model.build_token_values_v2( - token_data, CONF.identity.default_domain_id) - self.revoke_api.check_token(token_values) - - def validate_v2_token(self, token_id, belongs_to=None): - # NOTE(lbragstad): Only go to the persistence backend if the token - # provider requires it. - if self._needs_persistence: - # NOTE(morganfainberg): Ensure we never use the long-form token_id - # (PKI) as part of the cache_key. - unique_id = utils.generate_unique_id(token_id) - token_ref = self._persistence.get_token(unique_id) - token = self._validate_v2_token(token_ref) - else: - # NOTE(lbragstad): If the token doesn't require persistence, then - # it is a fernet token. The fernet token provider doesn't care if - # it's creating version 2.0 tokens or v3 tokens, so we use the same - # validate_non_persistent_token() method to validate both. Then we - # can leverage a separate method to make version 3 token data look - # like version 2.0 token data. The pattern we want to move towards - # is one where the token providers just handle data and the - # controller layers handle interpreting the token data in a format - # that makes sense for the request. - v3_token_ref = self.validate_non_persistent_token(token_id) - v2_token_data_helper = providers.common.V2TokenDataHelper() - token = v2_token_data_helper.v3_to_v2_token(v3_token_ref) - - # these are common things that happen regardless of token provider - token['access']['token']['id'] = token_id - self._token_belongs_to(token, belongs_to) - self._is_valid_token(token) - return token - - def check_revocation_v3(self, token): - try: - token_data = token['token'] - except KeyError: - raise exception.TokenNotFound(_('Failed to validate token')) - token_values = self.revoke_api.model.build_token_values(token_data) - self.revoke_api.check_token(token_values) - - def check_revocation(self, token): - version = self.get_token_version(token) - if version == V2: - return self.check_revocation_v2(token) - else: - return self.check_revocation_v3(token) - - def validate_v3_token(self, token_id): - if not token_id: - raise exception.TokenNotFound(_('No token in the request')) - - try: - # NOTE(lbragstad): Only go to persistent storage if we have a token - # to fetch from the backend (the driver persists the token). - # Otherwise the information about the token must be in the token - # id. - if not self._needs_persistence: - token_ref = self.validate_non_persistent_token(token_id) - else: - unique_id = utils.generate_unique_id(token_id) - # NOTE(morganfainberg): Ensure we never use the long-form - # token_id (PKI) as part of the cache_key. - token_ref = self._persistence.get_token(unique_id) - token_ref = self._validate_v3_token(token_ref) - self._is_valid_token(token_ref) - return token_ref - except exception.Unauthorized as e: - LOG.debug('Unable to validate token: %s', e) - raise exception.TokenNotFound(token_id=token_id) - - @MEMOIZE - def _validate_token(self, token_id): - if not token_id: - raise exception.TokenNotFound(_('No token in the request')) - - if not self._needs_persistence: - # NOTE(lbragstad): This will validate v2 and v3 non-persistent - # tokens. - return self.driver.validate_non_persistent_token(token_id) - token_ref = self._persistence.get_token(token_id) - version = self.get_token_version(token_ref) - if version == self.V3: - try: - return self.driver.validate_v3_token(token_ref) - except exception.Unauthorized as e: - LOG.debug('Unable to validate token: %s', e) - raise exception.TokenNotFound(token_id=token_id) - elif version == self.V2: - return self.driver.validate_v2_token(token_ref) - raise exception.UnsupportedTokenVersionException() - - @MEMOIZE - def _validate_v2_token(self, token_id): - return self.driver.validate_v2_token(token_id) - - @MEMOIZE - def _validate_v3_token(self, token_id): - return self.driver.validate_v3_token(token_id) - - def _is_valid_token(self, token): - """Verify the token is valid format and has not expired.""" - current_time = timeutils.normalize_time(timeutils.utcnow()) - - try: - # Get the data we need from the correct location (V2 and V3 tokens - # differ in structure, Try V3 first, fall back to V2 second) - token_data = token.get('token', token.get('access')) - expires_at = token_data.get('expires_at', - token_data.get('expires')) - if not expires_at: - expires_at = token_data['token']['expires'] - expiry = timeutils.normalize_time( - timeutils.parse_isotime(expires_at)) - except Exception: - LOG.exception(_LE('Unexpected error or malformed token ' - 'determining token expiry: %s'), token) - raise exception.TokenNotFound(_('Failed to validate token')) - - if current_time < expiry: - self.check_revocation(token) - # Token has not expired and has not been revoked. - return None - else: - raise exception.TokenNotFound(_('Failed to validate token')) - - def _token_belongs_to(self, token, belongs_to): - """Check if the token belongs to the right tenant. - - This is only used on v2 tokens. The structural validity of the token - will have already been checked before this method is called. - - """ - if belongs_to: - token_data = token['access']['token'] - if ('tenant' not in token_data or - token_data['tenant']['id'] != belongs_to): - raise exception.Unauthorized() - - def issue_v2_token(self, token_ref, roles_ref=None, catalog_ref=None): - token_id, token_data = self.driver.issue_v2_token( - token_ref, roles_ref, catalog_ref) - - if self._needs_persistence: - data = dict(key=token_id, - id=token_id, - expires=token_data['access']['token']['expires'], - user=token_ref['user'], - tenant=token_ref['tenant'], - metadata=token_ref['metadata'], - token_data=token_data, - bind=token_ref.get('bind'), - trust_id=token_ref['metadata'].get('trust_id'), - token_version=self.V2) - self._create_token(token_id, data) - - return token_id, token_data - - def issue_v3_token(self, user_id, method_names, expires_at=None, - project_id=None, domain_id=None, auth_context=None, - trust=None, metadata_ref=None, include_catalog=True, - parent_audit_id=None): - token_id, token_data = self.driver.issue_v3_token( - user_id, method_names, expires_at, project_id, domain_id, - auth_context, trust, metadata_ref, include_catalog, - parent_audit_id) - - if metadata_ref is None: - metadata_ref = {} - - if 'project' in token_data['token']: - # project-scoped token, fill in the v2 token data - # all we care are the role IDs - - # FIXME(gyee): is there really a need to store roles in metadata? - role_ids = [r['id'] for r in token_data['token']['roles']] - metadata_ref = {'roles': role_ids} - - if trust: - metadata_ref.setdefault('trust_id', trust['id']) - metadata_ref.setdefault('trustee_user_id', - trust['trustee_user_id']) - - data = dict(key=token_id, - id=token_id, - expires=token_data['token']['expires_at'], - user=token_data['token']['user'], - tenant=token_data['token'].get('project'), - metadata=metadata_ref, - token_data=token_data, - trust_id=trust['id'] if trust else None, - token_version=self.V3) - if self._needs_persistence: - self._create_token(token_id, data) - return token_id, token_data - - 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._validate_token.invalidate(self, token_id) - self._validate_v2_token.invalidate(self, token_id) - self._validate_v3_token.invalidate(self, token_id) - - def revoke_token(self, token_id, revoke_chain=False): - revoke_by_expires = False - project_id = None - domain_id = None - - token_ref = token_model.KeystoneToken( - token_id=token_id, - token_data=self.validate_token(token_id)) - - 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 - - if audit_id is None and not revoke_chain: - LOG.debug('Received token with no audit_id.') - revoke_by_expires = True - - if audit_chain_id is None and revoke_chain: - LOG.debug('Received token with no audit_chain_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 and self._needs_persistence: - self._persistence.delete_token(token_id=token_id) - - def list_revoked_tokens(self): - return self._persistence.list_revoked_tokens() - - def _trust_deleted_event_callback(self, service, resource_type, operation, - payload): - if CONF.token.revoke_by_id: - trust_id = payload['resource_info'] - trust = self.trust_api.get_trust(trust_id, deleted=True) - self._persistence.delete_tokens(user_id=trust['trustor_user_id'], - trust_id=trust_id) - - 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) - - 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) - - def _delete_user_project_tokens_callback(self, service, resource_type, - operation, payload): - if CONF.token.revoke_by_id: - user_id = payload['resource_info']['user_id'] - project_id = payload['resource_info']['project_id'] - self._persistence.delete_tokens_for_user(user_id=user_id, - project_id=project_id) - - def _delete_project_tokens_callback(self, service, resource_type, - operation, payload): - if CONF.token.revoke_by_id: - project_id = payload['resource_info'] - self._persistence.delete_tokens_for_users( - self.assignment_api.list_user_ids_for_project(project_id), - project_id=project_id) - - def _delete_user_oauth_consumer_tokens_callback(self, service, - resource_type, operation, - payload): - if CONF.token.revoke_by_id: - user_id = payload['resource_info']['user_id'] - consumer_id = payload['resource_info']['consumer_id'] - self._persistence.delete_tokens(user_id=user_id, - consumer_id=consumer_id) - - -@six.add_metaclass(abc.ABCMeta) -class Provider(object): - """Interface description for a Token provider.""" - - @abc.abstractmethod - def needs_persistence(self): - """Determine if the token should be persisted. - - If the token provider requires that the token be persisted to a - backend this should return True, otherwise return False. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def get_token_version(self, token_data): - """Return the version of the given token data. - - If the given token data is unrecognizable, - UnsupportedTokenVersionException is raised. - - :param token_data: token_data - :type token_data: dict - :returns: token version string - :raises keystone.exception.UnsupportedTokenVersionException: - If the token version is not expected. - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def issue_v2_token(self, token_ref, roles_ref=None, catalog_ref=None): - """Issue a V2 token. - - :param token_ref: token data to generate token from - :type token_ref: dict - :param roles_ref: optional roles list - :type roles_ref: dict - :param catalog_ref: optional catalog information - :type catalog_ref: dict - :returns: (token_id, token_data) - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def issue_v3_token(self, user_id, method_names, expires_at=None, - project_id=None, domain_id=None, auth_context=None, - trust=None, metadata_ref=None, include_catalog=True, - parent_audit_id=None): - """Issue a V3 Token. - - :param user_id: identity of the user - :type user_id: string - :param method_names: names of authentication methods - :type method_names: list - :param expires_at: optional time the token will expire - :type expires_at: string - :param project_id: optional project identity - :type project_id: string - :param domain_id: optional domain identity - :type domain_id: string - :param auth_context: optional context from the authorization plugins - :type auth_context: dict - :param trust: optional trust reference - :type trust: dict - :param metadata_ref: optional metadata reference - :type metadata_ref: dict - :param include_catalog: optional, include the catalog in token data - :type include_catalog: boolean - :param parent_audit_id: optional, the audit id of the parent token - :type parent_audit_id: string - :returns: (token_id, token_data) - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def validate_v2_token(self, token_ref): - """Validate the given V2 token and return the token data. - - Must raise Unauthorized exception if unable to validate token. - - :param token_ref: the token reference - :type token_ref: dict - :returns: token data - :raises keystone.exception.TokenNotFound: If the token doesn't exist. - - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def validate_non_persistent_token(self, token_id): - """Validate a given non-persistent token id and return the token_data. - - :param token_id: the token id - :type token_id: string - :returns: token data - :raises keystone.exception.TokenNotFound: When the token is invalid - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def validate_v3_token(self, token_ref): - """Validate the given V3 token and return the token_data. - - :param token_ref: the token reference - :type token_ref: dict - :returns: token data - :raises keystone.exception.TokenNotFound: If the token doesn't exist. - """ - raise exception.NotImplemented() # pragma: no cover - - @abc.abstractmethod - def _get_token_id(self, token_data): - """Generate the token_id based upon the data in token_data. - - :param token_data: token information - :type token_data: dict - :returns: token identifier - :rtype: six.text_type - """ - raise exception.NotImplemented() # pragma: no cover diff --git a/keystone-moon/keystone/token/providers/__init__.py b/keystone-moon/keystone/token/providers/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/keystone-moon/keystone/token/providers/__init__.py +++ /dev/null diff --git a/keystone-moon/keystone/token/providers/common.py b/keystone-moon/keystone/token/providers/common.py deleted file mode 100644 index 94729178..00000000 --- a/keystone-moon/keystone/token/providers/common.py +++ /dev/null @@ -1,808 +0,0 @@ -# Copyright 2013 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. - -from oslo_config import cfg -from oslo_log import log -from oslo_serialization import jsonutils -import six -from six.moves.urllib import parse - -from keystone.common import controller as common_controller -from keystone.common import dependency -from keystone.common import utils -from keystone import exception -from keystone.federation import constants as federation_constants -from keystone.i18n import _, _LE -from keystone import token -from keystone.token import provider - - -LOG = log.getLogger(__name__) -CONF = cfg.CONF - - -@dependency.requires('catalog_api', 'resource_api', 'assignment_api') -class V2TokenDataHelper(object): - """Creates V2 token data.""" - - def v3_to_v2_token(self, v3_token_data): - """Convert v3 token data into v2.0 token data. - - This method expects a dictionary generated from - V3TokenDataHelper.get_token_data() and converts it to look like a v2.0 - token dictionary. - - :param v3_token_data: dictionary formatted for v3 tokens - :returns: dictionary formatted for v2 tokens - :raises keystone.exception.Unauthorized: If a specific token type is - not supported in v2. - - """ - token_data = {} - # Build v2 token - v3_token = v3_token_data['token'] - - # NOTE(lbragstad): Version 2.0 tokens don't know about any domain other - # than the default domain specified in the configuration. - domain_id = v3_token.get('domain', {}).get('id') - if domain_id and CONF.identity.default_domain_id != domain_id: - msg = ('Unable to validate domain-scoped tokens outside of the ' - 'default domain') - raise exception.Unauthorized(msg) - - token = {} - token['expires'] = v3_token.get('expires_at') - token['issued_at'] = v3_token.get('issued_at') - token['audit_ids'] = v3_token.get('audit_ids') - - if 'project' in v3_token: - # v3 token_data does not contain all tenant attributes - tenant = self.resource_api.get_project( - v3_token['project']['id']) - # Drop domain specific fields since v2 calls are not domain-aware. - token['tenant'] = common_controller.V2Controller.v3_to_v2_project( - tenant) - token_data['token'] = token - - # Build v2 user - v3_user = v3_token['user'] - - user = common_controller.V2Controller.v3_to_v2_user(v3_user) - - if 'OS-TRUST:trust' in v3_token: - msg = ('Unable to validate trust-scoped tokens using version v2.0 ' - 'API.') - raise exception.Unauthorized(msg) - - if 'OS-OAUTH1' in v3_token: - msg = ('Unable to validate Oauth tokens using the version v2.0 ' - 'API.') - raise exception.Unauthorized(msg) - - # Set user roles - user['roles'] = [] - role_ids = [] - for role in v3_token.get('roles', []): - role_ids.append(role.pop('id')) - user['roles'].append(role) - user['roles_links'] = [] - - token_data['user'] = user - - # Get and build v2 service catalog - token_data['serviceCatalog'] = [] - if 'tenant' in token: - catalog_ref = self.catalog_api.get_catalog( - user['id'], token['tenant']['id']) - if catalog_ref: - token_data['serviceCatalog'] = self.format_catalog(catalog_ref) - - # Build v2 metadata - metadata = {} - metadata['roles'] = role_ids - # Setting is_admin to keep consistency in v2 response - metadata['is_admin'] = 0 - token_data['metadata'] = metadata - - return {'access': token_data} - - @classmethod - def format_token(cls, token_ref, roles_ref=None, catalog_ref=None, - trust_ref=None): - audit_info = None - user_ref = token_ref['user'] - metadata_ref = token_ref['metadata'] - if roles_ref is None: - roles_ref = [] - expires = token_ref.get('expires', provider.default_expire_time()) - if expires is not None: - if not isinstance(expires, six.text_type): - expires = utils.isotime(expires) - - token_data = token_ref.get('token_data') - if token_data: - token_audit = token_data.get( - 'access', token_data).get('token', {}).get('audit_ids') - audit_info = token_audit - - if audit_info is None: - audit_info = provider.audit_info(token_ref.get('parent_audit_id')) - - o = {'access': {'token': {'id': token_ref['id'], - 'expires': expires, - 'issued_at': utils.isotime(subsecond=True), - 'audit_ids': audit_info - }, - 'user': {'id': user_ref['id'], - 'name': user_ref['name'], - 'username': user_ref['name'], - 'roles': roles_ref, - 'roles_links': metadata_ref.get('roles_links', - []) - } - } - } - if 'bind' in token_ref: - o['access']['token']['bind'] = token_ref['bind'] - if 'tenant' in token_ref and token_ref['tenant']: - token_ref['tenant']['enabled'] = True - o['access']['token']['tenant'] = token_ref['tenant'] - if catalog_ref is not None: - o['access']['serviceCatalog'] = V2TokenDataHelper.format_catalog( - catalog_ref) - if metadata_ref: - if 'is_admin' in metadata_ref: - o['access']['metadata'] = {'is_admin': - metadata_ref['is_admin']} - else: - o['access']['metadata'] = {'is_admin': 0} - if 'roles' in metadata_ref: - o['access']['metadata']['roles'] = metadata_ref['roles'] - if CONF.trust.enabled and trust_ref: - o['access']['trust'] = {'trustee_user_id': - trust_ref['trustee_user_id'], - 'id': trust_ref['id'], - 'trustor_user_id': - trust_ref['trustor_user_id'], - 'impersonation': - trust_ref['impersonation'] - } - return o - - @classmethod - def format_catalog(cls, catalog_ref): - """Munge catalogs from internal to output format. - - Internal catalogs look like:: - - {$REGION: { - {$SERVICE: { - $key1: $value1, - ... - } - } - } - - The legacy api wants them to look like:: - - [{'name': $SERVICE[name], - 'type': $SERVICE, - 'endpoints': [{ - 'tenantId': $tenant_id, - ... - 'region': $REGION, - }], - 'endpoints_links': [], - }] - - """ - if not catalog_ref: - return [] - - services = {} - for region, region_ref in catalog_ref.items(): - for service, service_ref in region_ref.items(): - new_service_ref = services.get(service, {}) - new_service_ref['name'] = service_ref.pop('name') - new_service_ref['type'] = service - new_service_ref['endpoints_links'] = [] - service_ref['region'] = region - - endpoints_ref = new_service_ref.get('endpoints', []) - endpoints_ref.append(service_ref) - - new_service_ref['endpoints'] = endpoints_ref - services[service] = new_service_ref - - return list(services.values()) - - -@dependency.requires('assignment_api', 'catalog_api', 'federation_api', - 'identity_api', 'resource_api', 'role_api', 'trust_api') -class V3TokenDataHelper(object): - """Token data helper.""" - - def __init__(self): - # Keep __init__ around to ensure dependency injection works. - super(V3TokenDataHelper, self).__init__() - - def _get_filtered_domain(self, domain_id): - domain_ref = self.resource_api.get_domain(domain_id) - return {'id': domain_ref['id'], 'name': domain_ref['name']} - - def _get_filtered_project(self, project_id): - project_ref = self.resource_api.get_project(project_id) - filtered_project = { - 'id': project_ref['id'], - 'name': project_ref['name']} - if project_ref['domain_id'] is not None: - filtered_project['domain'] = ( - self._get_filtered_domain(project_ref['domain_id'])) - else: - # Projects acting as a domain do not have a domain_id attribute - filtered_project['domain'] = None - return filtered_project - - def _populate_scope(self, token_data, domain_id, project_id): - if 'domain' in token_data or 'project' in token_data: - # scope already exist, no need to populate it again - return - - if domain_id: - token_data['domain'] = self._get_filtered_domain(domain_id) - if project_id: - token_data['project'] = self._get_filtered_project(project_id) - - def _populate_is_admin_project(self, token_data): - # TODO(ayoung): Support the ability for a project acting as a domain - # to be the admin project once the rest of the code for projects - # acting as domains is merged. Code will likely be: - # (r.admin_project_name == None and project['is_domain'] == True - # and project['name'] == r.admin_project_domain_name) - project = token_data['project'] - r = CONF.resource - if (project['name'] == r.admin_project_name and - project['domain']['name'] == r.admin_project_domain_name): - token_data['is_admin_project'] = True - - def _get_roles_for_user(self, user_id, domain_id, project_id): - roles = [] - if domain_id: - roles = self.assignment_api.get_roles_for_user_and_domain( - user_id, domain_id) - if project_id: - roles = self.assignment_api.get_roles_for_user_and_project( - user_id, project_id) - return [self.role_api.get_role(role_id) for role_id in roles] - - def populate_roles_for_groups(self, token_data, group_ids, - project_id=None, domain_id=None, - user_id=None): - """Populate roles basing on provided groups and project/domain - - Used for ephemeral users with dynamically assigned groups. - This method does not return anything, yet it modifies token_data in - place. - - :param token_data: a dictionary used for building token response - :param group_ids: list of group IDs a user is a member of - :param project_id: project ID to scope to - :param domain_id: domain ID to scope to - :param user_id: user ID - - :raises keystone.exception.Unauthorized: when no roles were found for a - (group_ids, project_id) or (group_ids, domain_id) pairs. - - """ - def check_roles(roles, user_id, project_id, domain_id): - # User was granted roles so simply exit this function. - if roles: - return - if project_id: - msg = _('User %(user_id)s has no access ' - 'to project %(project_id)s') % { - 'user_id': user_id, - 'project_id': project_id} - elif domain_id: - msg = _('User %(user_id)s has no access ' - 'to domain %(domain_id)s') % { - 'user_id': user_id, - 'domain_id': domain_id} - # Since no roles were found a user is not authorized to - # perform any operations. Raise an exception with - # appropriate error message. - raise exception.Unauthorized(msg) - - roles = self.assignment_api.get_roles_for_groups(group_ids, - project_id, - domain_id) - check_roles(roles, user_id, project_id, domain_id) - token_data['roles'] = roles - - def _populate_user(self, token_data, user_id, trust): - if 'user' in token_data: - # no need to repopulate user if it already exists - return - - user_ref = self.identity_api.get_user(user_id) - if CONF.trust.enabled and trust and 'OS-TRUST:trust' not in token_data: - trustor_user_ref = (self.identity_api.get_user( - trust['trustor_user_id'])) - try: - self.identity_api.assert_user_enabled(trust['trustor_user_id']) - except AssertionError: - raise exception.Forbidden(_('Trustor is disabled.')) - if trust['impersonation']: - user_ref = trustor_user_ref - token_data['OS-TRUST:trust'] = ( - { - 'id': trust['id'], - 'trustor_user': {'id': trust['trustor_user_id']}, - 'trustee_user': {'id': trust['trustee_user_id']}, - 'impersonation': trust['impersonation'] - }) - filtered_user = { - 'id': user_ref['id'], - 'name': user_ref['name'], - 'domain': self._get_filtered_domain(user_ref['domain_id'])} - token_data['user'] = filtered_user - - def _populate_oauth_section(self, token_data, access_token): - if access_token: - access_token_id = access_token['id'] - consumer_id = access_token['consumer_id'] - token_data['OS-OAUTH1'] = ({'access_token_id': access_token_id, - 'consumer_id': consumer_id}) - - def _populate_roles(self, token_data, user_id, domain_id, project_id, - trust, access_token): - if 'roles' in token_data: - # no need to repopulate roles - return - - if access_token: - filtered_roles = [] - authed_role_ids = jsonutils.loads(access_token['role_ids']) - all_roles = self.role_api.list_roles() - for role in all_roles: - for authed_role in authed_role_ids: - if authed_role == role['id']: - filtered_roles.append({'id': role['id'], - 'name': role['name']}) - token_data['roles'] = filtered_roles - return - - if CONF.trust.enabled and trust: - # If redelegated_trust_id is set, then we must traverse the - # trust_chain in order to determine who the original trustor is. We - # need to do this because the user ID of the original trustor helps - # us determine scope in the redelegated context. - if trust.get('redelegated_trust_id'): - trust_chain = self.trust_api.get_trust_pedigree(trust['id']) - token_user_id = trust_chain[-1]['trustor_user_id'] - else: - token_user_id = trust['trustor_user_id'] - - token_project_id = trust['project_id'] - # trusts do not support domains yet - token_domain_id = None - else: - token_user_id = user_id - token_project_id = project_id - token_domain_id = domain_id - - if token_domain_id or token_project_id: - filtered_roles = [] - if CONF.trust.enabled and trust: - # First expand out any roles that were in the trust to include - # any implied roles, whether global or domain specific - refs = [{'role_id': role['id']} for role in trust['roles']] - effective_trust_roles = ( - self.assignment_api.add_implied_roles(refs)) - # Now get the current role assignments for the trustor, - # including any domain specific roles. - assignment_list = self.assignment_api.list_role_assignments( - user_id=token_user_id, - project_id=token_project_id, - effective=True, strip_domain_roles=False) - current_effective_trustor_roles = ( - list(set([x['role_id'] for x in assignment_list]))) - # Go through each of the effective trust roles, making sure the - # trustor still has them, if any have been removed, then we - # will treat the trust as invalid - for trust_role in effective_trust_roles: - - match_roles = [x for x in current_effective_trustor_roles - if x == trust_role['role_id']] - if match_roles: - role = self.role_api.get_role(match_roles[0]) - if role['domain_id'] is None: - filtered_roles.append(role) - else: - raise exception.Forbidden( - _('Trustee has no delegated roles.')) - else: - for role in self._get_roles_for_user(token_user_id, - token_domain_id, - token_project_id): - filtered_roles.append({'id': role['id'], - 'name': role['name']}) - - # user has no project or domain roles, therefore access denied - if not filtered_roles: - if token_project_id: - msg = _('User %(user_id)s has no access ' - 'to project %(project_id)s') % { - 'user_id': user_id, - 'project_id': token_project_id} - else: - msg = _('User %(user_id)s has no access ' - 'to domain %(domain_id)s') % { - 'user_id': user_id, - 'domain_id': token_domain_id} - LOG.debug(msg) - raise exception.Unauthorized(msg) - - token_data['roles'] = filtered_roles - - def _populate_service_catalog(self, token_data, user_id, - domain_id, project_id, trust): - if 'catalog' in token_data: - # no need to repopulate service catalog - return - - if CONF.trust.enabled and trust: - user_id = trust['trustor_user_id'] - if project_id or domain_id: - service_catalog = self.catalog_api.get_v3_catalog( - user_id, project_id) - token_data['catalog'] = service_catalog - - def _populate_service_providers(self, token_data): - if 'service_providers' in token_data: - return - - service_providers = self.federation_api.get_enabled_service_providers() - if service_providers: - token_data['service_providers'] = service_providers - - def _populate_token_dates(self, token_data, expires=None, trust=None, - issued_at=None): - if not expires: - expires = provider.default_expire_time() - if not isinstance(expires, six.string_types): - expires = utils.isotime(expires, subsecond=True) - token_data['expires_at'] = expires - token_data['issued_at'] = (issued_at or - utils.isotime(subsecond=True)) - - def _populate_audit_info(self, token_data, audit_info=None): - if audit_info is None or isinstance(audit_info, six.string_types): - token_data['audit_ids'] = provider.audit_info(audit_info) - elif isinstance(audit_info, list): - token_data['audit_ids'] = audit_info - else: - msg = (_('Invalid audit info data type: %(data)s (%(type)s)') % - {'data': audit_info, 'type': type(audit_info)}) - LOG.error(msg) - raise exception.UnexpectedError(msg) - - def get_token_data(self, user_id, method_names, domain_id=None, - project_id=None, expires=None, trust=None, token=None, - include_catalog=True, bind=None, access_token=None, - issued_at=None, audit_info=None): - token_data = {'methods': method_names} - - # We've probably already written these to the token - if token: - for x in ('roles', 'user', 'catalog', 'project', 'domain'): - if x in token: - token_data[x] = token[x] - - if bind: - token_data['bind'] = bind - - self._populate_scope(token_data, domain_id, project_id) - if token_data.get('project'): - self._populate_is_admin_project(token_data) - self._populate_user(token_data, user_id, trust) - self._populate_roles(token_data, user_id, domain_id, project_id, trust, - access_token) - self._populate_audit_info(token_data, audit_info) - - if include_catalog: - self._populate_service_catalog(token_data, user_id, domain_id, - project_id, trust) - self._populate_service_providers(token_data) - self._populate_token_dates(token_data, expires=expires, trust=trust, - issued_at=issued_at) - self._populate_oauth_section(token_data, access_token) - return {'token': token_data} - - -@dependency.requires('catalog_api', 'identity_api', 'oauth_api', - 'resource_api', 'role_api', 'trust_api') -class BaseProvider(provider.Provider): - def __init__(self, *args, **kwargs): - super(BaseProvider, self).__init__(*args, **kwargs) - self.v3_token_data_helper = V3TokenDataHelper() - self.v2_token_data_helper = V2TokenDataHelper() - - def get_token_version(self, token_data): - if token_data and isinstance(token_data, dict): - if 'token_version' in token_data: - if token_data['token_version'] in token.provider.VERSIONS: - return token_data['token_version'] - # FIXME(morganfainberg): deprecate the following logic in future - # revisions. It is better to just specify the token_version in - # the token_data itself. This way we can support future versions - # that might have the same fields. - if 'access' in token_data: - return token.provider.V2 - if 'token' in token_data and 'methods' in token_data['token']: - return token.provider.V3 - raise exception.UnsupportedTokenVersionException() - - def issue_v2_token(self, token_ref, roles_ref=None, - catalog_ref=None): - if token_ref.get('bind') and not self._supports_bind_authentication: - msg = _('The configured token provider does not support bind ' - 'authentication.') - raise exception.NotImplemented(message=msg) - - metadata_ref = token_ref['metadata'] - trust_ref = None - if CONF.trust.enabled and metadata_ref and 'trust_id' in metadata_ref: - trust_ref = self.trust_api.get_trust(metadata_ref['trust_id']) - - token_data = self.v2_token_data_helper.format_token( - token_ref, roles_ref, catalog_ref, trust_ref) - token_id = self._get_token_id(token_data) - token_data['access']['token']['id'] = token_id - return token_id, token_data - - def _is_mapped_token(self, auth_context): - return (federation_constants.IDENTITY_PROVIDER in auth_context and - federation_constants.PROTOCOL in auth_context) - - def issue_v3_token(self, user_id, method_names, expires_at=None, - project_id=None, domain_id=None, auth_context=None, - trust=None, metadata_ref=None, include_catalog=True, - parent_audit_id=None): - if auth_context and auth_context.get('bind'): - # NOTE(lbragstad): Check if the token provider being used actually - # supports bind authentication methods before proceeding. - if not self._supports_bind_authentication: - raise exception.NotImplemented(_( - 'The configured token provider does not support bind ' - 'authentication.')) - - # for V2, trust is stashed in metadata_ref - if (CONF.trust.enabled and not trust and metadata_ref and - 'trust_id' in metadata_ref): - trust = self.trust_api.get_trust(metadata_ref['trust_id']) - - if CONF.trust.enabled and trust: - if user_id != trust['trustee_user_id']: - raise exception.Forbidden(_('User is not a trustee.')) - - token_ref = None - if auth_context and self._is_mapped_token(auth_context): - token_ref = self._handle_mapped_tokens( - auth_context, project_id, domain_id) - - access_token = None - if 'oauth1' in method_names: - access_token_id = auth_context['access_token_id'] - access_token = self.oauth_api.get_access_token(access_token_id) - - token_data = self.v3_token_data_helper.get_token_data( - user_id, - method_names, - domain_id=domain_id, - project_id=project_id, - expires=expires_at, - trust=trust, - bind=auth_context.get('bind') if auth_context else None, - token=token_ref, - include_catalog=include_catalog, - access_token=access_token, - audit_info=parent_audit_id) - - token_id = self._get_token_id(token_data) - return token_id, token_data - - def _handle_mapped_tokens(self, auth_context, project_id, domain_id): - user_id = auth_context['user_id'] - group_ids = auth_context['group_ids'] - idp = auth_context[federation_constants.IDENTITY_PROVIDER] - protocol = auth_context[federation_constants.PROTOCOL] - token_data = { - 'user': { - 'id': user_id, - 'name': parse.unquote(user_id), - federation_constants.FEDERATION: { - 'groups': [{'id': x} for x in group_ids], - 'identity_provider': {'id': idp}, - 'protocol': {'id': protocol} - }, - 'domain': { - 'id': CONF.federation.federated_domain_name, - 'name': CONF.federation.federated_domain_name - } - } - } - - if project_id or domain_id: - self.v3_token_data_helper.populate_roles_for_groups( - token_data, group_ids, project_id, domain_id, user_id) - - return token_data - - def _verify_token_ref(self, token_ref): - """Verify and return the given token_ref.""" - if not token_ref: - raise exception.Unauthorized() - return token_ref - - def _assert_is_not_federation_token(self, token_ref): - """Make sure we aren't using v2 auth on a federation token.""" - token_data = token_ref.get('token_data') - if (token_data and self.get_token_version(token_data) == - token.provider.V3): - if 'OS-FEDERATION' in token_data['token']['user']: - msg = _('Attempting to use OS-FEDERATION token with V2 ' - 'Identity Service, use V3 Authentication') - raise exception.Unauthorized(msg) - - def _assert_default_domain(self, token_ref): - """Make sure we are operating on default domain only.""" - if (token_ref.get('token_data') and - self.get_token_version(token_ref.get('token_data')) == - token.provider.V3): - # this is a V3 token - msg = _('Non-default domain is not supported') - # domain scoping is prohibited - if token_ref['token_data']['token'].get('domain'): - raise exception.Unauthorized( - _('Domain scoped token is not supported')) - # if token is scoped to trust, both trustor and trustee must - # be in the default domain. Furthermore, the delegated project - # must also be in the default domain - metadata_ref = token_ref['metadata'] - if CONF.trust.enabled and 'trust_id' in metadata_ref: - trust_ref = self.trust_api.get_trust(metadata_ref['trust_id']) - trustee_user_ref = self.identity_api.get_user( - trust_ref['trustee_user_id']) - if (trustee_user_ref['domain_id'] != - CONF.identity.default_domain_id): - raise exception.Unauthorized(msg) - trustor_user_ref = self.identity_api.get_user( - trust_ref['trustor_user_id']) - if (trustor_user_ref['domain_id'] != - CONF.identity.default_domain_id): - raise exception.Unauthorized(msg) - project_ref = self.resource_api.get_project( - trust_ref['project_id']) - if (project_ref['domain_id'] != - CONF.identity.default_domain_id): - raise exception.Unauthorized(msg) - - def validate_v2_token(self, token_ref): - try: - self._assert_is_not_federation_token(token_ref) - self._assert_default_domain(token_ref) - # FIXME(gyee): performance or correctness? Should we return the - # cached token or reconstruct it? Obviously if we are going with - # the cached token, any role, project, or domain name changes - # will not be reflected. One may argue that with PKI tokens, - # we are essentially doing cached token validation anyway. - # Lets go with the cached token strategy. Since token - # management layer is now pluggable, one can always provide - # their own implementation to suit their needs. - token_data = token_ref.get('token_data') - if (self.get_token_version(token_data) != token.provider.V2): - # Validate the V3 token as V2 - token_data = self.v2_token_data_helper.v3_to_v2_token( - token_data) - - trust_id = token_data['access'].get('trust', {}).get('id') - if trust_id: - msg = ('Unable to validate trust-scoped tokens using version ' - 'v2.0 API.') - raise exception.Unauthorized(msg) - - return token_data - except exception.ValidationError: - LOG.exception(_LE('Failed to validate token')) - token_id = token_ref['token_data']['access']['token']['id'] - raise exception.TokenNotFound(token_id=token_id) - - def validate_non_persistent_token(self, token_id): - try: - (user_id, methods, audit_ids, domain_id, project_id, trust_id, - federated_info, access_token_id, created_at, expires_at) = ( - self.token_formatter.validate_token(token_id)) - except exception.ValidationError as e: - raise exception.TokenNotFound(e) - - token_dict = None - trust_ref = None - if federated_info: - # NOTE(lbragstad): We need to rebuild information about the - # federated token as well as the federated token roles. This is - # because when we validate a non-persistent token, we don't have a - # token reference to pull the federated token information out of. - # As a result, we have to extract it from the token itself and - # rebuild the federated context. These private methods currently - # live in the keystone.token.providers.fernet.Provider() class. - token_dict = self._rebuild_federated_info(federated_info, user_id) - if project_id or domain_id: - self._rebuild_federated_token_roles(token_dict, federated_info, - user_id, project_id, - domain_id) - if trust_id: - trust_ref = self.trust_api.get_trust(trust_id) - - access_token = None - if access_token_id: - access_token = self.oauth_api.get_access_token(access_token_id) - - return self.v3_token_data_helper.get_token_data( - user_id, - method_names=methods, - domain_id=domain_id, - project_id=project_id, - issued_at=created_at, - expires=expires_at, - trust=trust_ref, - token=token_dict, - access_token=access_token, - audit_info=audit_ids) - - def validate_v3_token(self, token_ref): - # FIXME(gyee): performance or correctness? Should we return the - # cached token or reconstruct it? Obviously if we are going with - # the cached token, any role, project, or domain name changes - # will not be reflected. One may argue that with PKI tokens, - # we are essentially doing cached token validation anyway. - # Lets go with the cached token strategy. Since token - # management layer is now pluggable, one can always provide - # their own implementation to suit their needs. - - trust_id = token_ref.get('trust_id') - if trust_id: - # token trust validation - self.trust_api.get_trust(trust_id) - - token_data = token_ref.get('token_data') - if not token_data or 'token' not in token_data: - # token ref is created by V2 API - project_id = None - project_ref = token_ref.get('tenant') - if project_ref: - project_id = project_ref['id'] - - issued_at = token_ref['token_data']['access']['token']['issued_at'] - audit = token_ref['token_data']['access']['token'].get('audit_ids') - - token_data = self.v3_token_data_helper.get_token_data( - token_ref['user']['id'], - ['password', 'token'], - project_id=project_id, - bind=token_ref.get('bind'), - expires=token_ref['expires'], - issued_at=issued_at, - audit_info=audit) - return token_data diff --git a/keystone-moon/keystone/token/providers/fernet/__init__.py b/keystone-moon/keystone/token/providers/fernet/__init__.py deleted file mode 100644 index 953ef624..00000000 --- a/keystone-moon/keystone/token/providers/fernet/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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. - -from keystone.token.providers.fernet.core import * # noqa diff --git a/keystone-moon/keystone/token/providers/fernet/core.py b/keystone-moon/keystone/token/providers/fernet/core.py deleted file mode 100644 index ff6fe9cc..00000000 --- a/keystone-moon/keystone/token/providers/fernet/core.py +++ /dev/null @@ -1,211 +0,0 @@ -# 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. - -from oslo_config import cfg - -from keystone.common import dependency -from keystone.common import utils as ks_utils -from keystone.federation import constants as federation_constants -from keystone.token.providers import common -from keystone.token.providers.fernet import token_formatters as tf - - -CONF = cfg.CONF - - -@dependency.requires('trust_api', 'oauth_api') -class Provider(common.BaseProvider): - def __init__(self, *args, **kwargs): - super(Provider, self).__init__(*args, **kwargs) - - self.token_formatter = tf.TokenFormatter() - - def needs_persistence(self): - """Should the token be written to a backend.""" - return False - - def issue_v2_token(self, *args, **kwargs): - token_id, token_data = super(Provider, self).issue_v2_token( - *args, **kwargs) - self._build_issued_at_info(token_id, token_data) - return token_id, token_data - - def issue_v3_token(self, *args, **kwargs): - token_id, token_data = super(Provider, self).issue_v3_token( - *args, **kwargs) - self._build_issued_at_info(token_id, token_data) - return token_id, token_data - - def _build_issued_at_info(self, token_id, token_data): - # NOTE(roxanaghe, lbragstad): We must use the creation time that - # Fernet builds into it's token. The Fernet spec details that the - # token creation time is built into the token, outside of the payload - # provided by Keystone. This is the reason why we don't pass the - # issued_at time in the payload. This also means that we shouldn't - # return a token reference with a creation time that we created - # when Fernet uses a different creation time. We should use the - # creation time provided by Fernet because it's the creation time - # that we have to rely on when we validate the token. - fernet_creation_datetime_obj = self.token_formatter.creation_time( - token_id) - if token_data.get('access'): - token_data['access']['token']['issued_at'] = ks_utils.isotime( - at=fernet_creation_datetime_obj, subsecond=True) - else: - token_data['token']['issued_at'] = ks_utils.isotime( - at=fernet_creation_datetime_obj, subsecond=True) - - def _build_federated_info(self, token_data): - """Extract everything needed for federated tokens. - - This dictionary is passed to federated token formatters, which unpack - the values and build federated Fernet tokens. - - """ - token_data = token_data['token'] - try: - user = token_data['user'] - federation = user[federation_constants.FEDERATION] - idp_id = federation['identity_provider']['id'] - protocol_id = federation['protocol']['id'] - except KeyError: - # The token data doesn't have federated info, so we aren't dealing - # with a federated token and no federated info to build. - return - - group_ids = federation.get('groups') - - return {'group_ids': group_ids, - 'idp_id': idp_id, - 'protocol_id': protocol_id} - - def _rebuild_federated_info(self, federated_dict, user_id): - """Format federated information into the token reference. - - The federated_dict is passed back from the federated token formatters. - The responsibility of this method is to format the information passed - back from the token formatter into the token reference before - constructing the token data from the V3TokenDataHelper. - - """ - g_ids = federated_dict['group_ids'] - idp_id = federated_dict['idp_id'] - protocol_id = federated_dict['protocol_id'] - - federated_info = { - 'groups': g_ids, - 'identity_provider': {'id': idp_id}, - 'protocol': {'id': protocol_id} - } - - token_dict = { - 'user': { - federation_constants.FEDERATION: federated_info, - 'id': user_id, - 'name': user_id, - 'domain': {'id': CONF.federation.federated_domain_name, - 'name': CONF.federation.federated_domain_name, }, - } - } - - return token_dict - - def _rebuild_federated_token_roles(self, token_dict, federated_dict, - user_id, project_id, domain_id): - """Populate roles based on (groups, project/domain) pair. - - We must populate roles from (groups, project/domain) as ephemeral users - don't exist in the backend. Upon success, a ``roles`` key will be added - to ``token_dict``. - - :param token_dict: dictionary with data used for building token - :param federated_dict: federated information such as identity provider - protocol and set of group IDs - :param user_id: user ID - :param project_id: project ID the token is being scoped to - :param domain_id: domain ID the token is being scoped to - - """ - group_ids = [x['id'] for x in federated_dict['group_ids']] - self.v3_token_data_helper.populate_roles_for_groups( - token_dict, group_ids, project_id, domain_id, user_id) - - def _extract_v2_token_data(self, token_data): - user_id = token_data['access']['user']['id'] - expires_at = token_data['access']['token']['expires'] - audit_ids = token_data['access']['token'].get('audit_ids') - methods = ['password'] - if len(audit_ids) > 1: - methods.append('token') - project_id = token_data['access']['token'].get('tenant', {}).get('id') - domain_id = None - trust_id = None - access_token_id = None - federated_info = None - return (user_id, expires_at, audit_ids, methods, domain_id, project_id, - trust_id, access_token_id, federated_info) - - def _extract_v3_token_data(self, token_data): - """Extract information from a v3 token reference.""" - user_id = token_data['token']['user']['id'] - expires_at = token_data['token']['expires_at'] - audit_ids = token_data['token']['audit_ids'] - methods = token_data['token'].get('methods') - domain_id = token_data['token'].get('domain', {}).get('id') - project_id = token_data['token'].get('project', {}).get('id') - trust_id = token_data['token'].get('OS-TRUST:trust', {}).get('id') - access_token_id = token_data['token'].get('OS-OAUTH1', {}).get( - 'access_token_id') - federated_info = self._build_federated_info(token_data) - - return (user_id, expires_at, audit_ids, methods, domain_id, project_id, - trust_id, access_token_id, federated_info) - - def _get_token_id(self, token_data): - """Generate the token_id based upon the data in token_data. - - :param token_data: token information - :type token_data: dict - :rtype: six.text_type - - """ - # NOTE(lbragstad): Only v2.0 token responses include an 'access' - # attribute. - if token_data.get('access'): - (user_id, expires_at, audit_ids, methods, domain_id, project_id, - trust_id, access_token_id, federated_info) = ( - self._extract_v2_token_data(token_data)) - else: - (user_id, expires_at, audit_ids, methods, domain_id, project_id, - trust_id, access_token_id, federated_info) = ( - self._extract_v3_token_data(token_data)) - - return self.token_formatter.create_token( - user_id, - expires_at, - audit_ids, - methods=methods, - domain_id=domain_id, - project_id=project_id, - trust_id=trust_id, - federated_info=federated_info, - access_token_id=access_token_id - ) - - @property - def _supports_bind_authentication(self): - """Return if the token provider supports bind authentication methods. - - :returns: False - - """ - return False diff --git a/keystone-moon/keystone/token/providers/fernet/token_formatters.py b/keystone-moon/keystone/token/providers/fernet/token_formatters.py deleted file mode 100644 index dfdd06e8..00000000 --- a/keystone-moon/keystone/token/providers/fernet/token_formatters.py +++ /dev/null @@ -1,677 +0,0 @@ -# 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 base64 -import datetime -import struct -import uuid - -from cryptography import fernet -import msgpack -from oslo_config import cfg -from oslo_log import log -from oslo_utils import timeutils -from six.moves import map -from six.moves import urllib - -from keystone.auth import plugins as auth_plugins -from keystone.common import utils as ks_utils -from keystone import exception -from keystone.i18n import _, _LI -from keystone.token import provider -from keystone.token.providers.fernet import utils - - -CONF = cfg.CONF -LOG = log.getLogger(__name__) - -# Fernet byte indexes as as computed by pypi/keyless_fernet and defined in -# https://github.com/fernet/spec -TIMESTAMP_START = 1 -TIMESTAMP_END = 9 - - -class TokenFormatter(object): - """Packs and unpacks payloads into tokens for transport.""" - - @property - def crypto(self): - """Return a cryptography instance. - - You can extend this class with a custom crypto @property to provide - your own token encoding / decoding. For example, using a different - cryptography library (e.g. ``python-keyczar``) or to meet arbitrary - security requirements. - - This @property just needs to return an object that implements - ``encrypt(plaintext)`` and ``decrypt(ciphertext)``. - - """ - keys = utils.load_keys() - - if not keys: - raise exception.KeysNotFound() - - fernet_instances = [fernet.Fernet(key) for key in keys] - return fernet.MultiFernet(fernet_instances) - - def pack(self, payload): - """Pack a payload for transport as a token. - - :type payload: six.binary_type - :rtype: six.text_type - - """ - # base64 padding (if any) is not URL-safe - return self.crypto.encrypt(payload).rstrip(b'=').decode('utf-8') - - def unpack(self, token): - """Unpack a token, and validate the payload. - - :type token: six.text_type - :rtype: six.binary_type - - """ - # TODO(lbragstad): Restore padding on token before decoding it. - # Initially in Kilo, Fernet tokens were returned to the user with - # padding appended to the token. Later in Liberty this padding was - # removed and restored in the Fernet provider. The following if - # statement ensures that we can validate tokens with and without token - # padding, in the event of an upgrade and the tokens that are issued - # throughout the upgrade. Remove this if statement when Mitaka opens - # for development and exclusively use the restore_padding() class - # method. - if token.endswith('%3D'): - token = urllib.parse.unquote(token) - else: - token = TokenFormatter.restore_padding(token) - - try: - return self.crypto.decrypt(token.encode('utf-8')) - except fernet.InvalidToken: - raise exception.ValidationError( - _('This is not a recognized Fernet token %s') % token) - - @classmethod - def restore_padding(cls, token): - """Restore padding based on token size. - - :param token: token to restore padding on - :type token: six.text_type - :returns: token with correct padding - - """ - # Re-inflate the padding - mod_returned = len(token) % 4 - if mod_returned: - missing_padding = 4 - mod_returned - token += '=' * missing_padding - return token - - @classmethod - def creation_time(cls, fernet_token): - """Returns the creation time of a valid Fernet token. - - :type fernet_token: six.text_type - - """ - fernet_token = TokenFormatter.restore_padding(fernet_token) - # fernet_token is six.text_type - - # Fernet tokens are base64 encoded, so we need to unpack them first - # urlsafe_b64decode() requires six.binary_type - token_bytes = base64.urlsafe_b64decode(fernet_token.encode('utf-8')) - - # slice into the byte array to get just the timestamp - timestamp_bytes = token_bytes[TIMESTAMP_START:TIMESTAMP_END] - - # convert those bytes to an integer - # (it's a 64-bit "unsigned long long int" in C) - timestamp_int = struct.unpack(">Q", timestamp_bytes)[0] - - # and with an integer, it's trivial to produce a datetime object - created_at = datetime.datetime.utcfromtimestamp(timestamp_int) - - return created_at - - def create_token(self, user_id, expires_at, audit_ids, methods=None, - domain_id=None, project_id=None, trust_id=None, - federated_info=None, access_token_id=None): - """Given a set of payload attributes, generate a Fernet token.""" - for payload_class in PAYLOAD_CLASSES: - if payload_class.create_arguments_apply( - project_id=project_id, domain_id=domain_id, - trust_id=trust_id, federated_info=federated_info, - access_token_id=access_token_id): - break - - version = payload_class.version - payload = payload_class.assemble( - user_id, methods, project_id, domain_id, expires_at, audit_ids, - trust_id, federated_info, access_token_id - ) - - versioned_payload = (version,) + payload - serialized_payload = msgpack.packb(versioned_payload) - token = self.pack(serialized_payload) - - # NOTE(lbragstad): We should warn against Fernet tokens that are over - # 255 characters in length. This is mostly due to persisting the tokens - # in a backend store of some kind that might have a limit of 255 - # characters. Even though Keystone isn't storing a Fernet token - # anywhere, we can't say it isn't being stored somewhere else with - # those kind of backend constraints. - if len(token) > 255: - LOG.info(_LI('Fernet token created with length of %d ' - 'characters, which exceeds 255 characters'), - len(token)) - - return token - - def validate_token(self, token): - """Validates a Fernet token and returns the payload attributes. - - :type token: six.text_type - - """ - serialized_payload = self.unpack(token) - versioned_payload = msgpack.unpackb(serialized_payload) - version, payload = versioned_payload[0], versioned_payload[1:] - - for payload_class in PAYLOAD_CLASSES: - if version == payload_class.version: - (user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id) = ( - payload_class.disassemble(payload)) - break - else: - # If the token_format is not recognized, raise ValidationError. - raise exception.ValidationError(_( - 'This is not a recognized Fernet payload version: %s') % - version) - - # rather than appearing in the payload, the creation time is encoded - # into the token format itself - created_at = TokenFormatter.creation_time(token) - created_at = ks_utils.isotime(at=created_at, subsecond=True) - expires_at = timeutils.parse_isotime(expires_at) - expires_at = ks_utils.isotime(at=expires_at, subsecond=True) - - return (user_id, methods, audit_ids, domain_id, project_id, trust_id, - federated_info, access_token_id, created_at, expires_at) - - -class BasePayload(object): - # each payload variant should have a unique version - version = None - - @classmethod - def create_arguments_apply(cls, **kwargs): - """Check the arguments to see if they apply to this payload variant. - - :returns: True if the arguments indicate that this payload class is - needed for the token otherwise returns False. - :rtype: bool - - """ - raise NotImplementedError() - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - """Assemble the payload of a token. - - :param user_id: identifier of the user in the token request - :param methods: list of authentication methods used - :param project_id: ID of the project to scope to - :param domain_id: ID of the domain to scope to - :param expires_at: datetime of the token's expiration - :param audit_ids: list of the token's audit IDs - :param trust_id: ID of the trust in effect - :param federated_info: dictionary containing group IDs, the identity - provider ID, protocol ID, and federated domain - ID - :param access_token_id: ID of the secret in OAuth1 authentication - :returns: the payload of a token - - """ - raise NotImplementedError() - - @classmethod - def disassemble(cls, payload): - """Disassemble an unscoped payload into the component data. - - The tuple consists of:: - - (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - * ``methods`` are the auth methods. - * federated_info is a dict contains the group IDs, the identity - provider ID, the protocol ID, and the federated domain ID - - Fields will be set to None if they didn't apply to this payload type. - - :param payload: this variant of payload - :returns: a tuple of the payloads component data - - """ - raise NotImplementedError() - - @classmethod - def convert_uuid_hex_to_bytes(cls, uuid_string): - """Compress UUID formatted strings to bytes. - - :param uuid_string: uuid string to compress to bytes - :returns: a byte representation of the uuid - - """ - uuid_obj = uuid.UUID(uuid_string) - return uuid_obj.bytes - - @classmethod - def convert_uuid_bytes_to_hex(cls, uuid_byte_string): - """Generate uuid.hex format based on byte string. - - :param uuid_byte_string: uuid string to generate from - :returns: uuid hex formatted string - - """ - uuid_obj = uuid.UUID(bytes=uuid_byte_string) - return uuid_obj.hex - - @classmethod - def _convert_time_string_to_float(cls, time_string): - """Convert a time formatted string to a float. - - :param time_string: time formatted string - :returns: a timestamp as a float - - """ - time_object = timeutils.parse_isotime(time_string) - return (timeutils.normalize_time(time_object) - - datetime.datetime.utcfromtimestamp(0)).total_seconds() - - @classmethod - def _convert_float_to_time_string(cls, time_float): - """Convert a floating point timestamp to a string. - - :param time_float: integer representing timestamp - :returns: a time formatted strings - - """ - time_object = datetime.datetime.utcfromtimestamp(time_float) - return ks_utils.isotime(time_object, subsecond=True) - - @classmethod - def attempt_convert_uuid_hex_to_bytes(cls, value): - """Attempt to convert value to bytes or return value. - - :param value: value to attempt to convert to bytes - :returns: tuple containing boolean indicating whether user_id was - stored as bytes and uuid value as bytes or the original value - - """ - try: - return (True, cls.convert_uuid_hex_to_bytes(value)) - except ValueError: - # this might not be a UUID, depending on the situation (i.e. - # federation) - return (False, value) - - -class UnscopedPayload(BasePayload): - version = 0 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return True - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - return (b_user_id, methods, expires_at_int, b_audit_ids) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - expires_at_str = cls._convert_float_to_time_string(payload[2]) - audit_ids = list(map(provider.base64_encode, payload[3])) - project_id = None - domain_id = None - trust_id = None - federated_info = None - access_token_id = None - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -class DomainScopedPayload(BasePayload): - version = 1 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['domain_id'] - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - try: - b_domain_id = cls.convert_uuid_hex_to_bytes(domain_id) - except ValueError: - # the default domain ID is configurable, and probably isn't a UUID - if domain_id == CONF.identity.default_domain_id: - b_domain_id = domain_id - else: - raise - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - return (b_user_id, methods, b_domain_id, expires_at_int, b_audit_ids) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - try: - domain_id = cls.convert_uuid_bytes_to_hex(payload[2]) - except ValueError: - # the default domain ID is configurable, and probably isn't a UUID - if payload[2] == CONF.identity.default_domain_id: - domain_id = payload[2] - else: - raise - expires_at_str = cls._convert_float_to_time_string(payload[3]) - audit_ids = list(map(provider.base64_encode, payload[4])) - project_id = None - trust_id = None - federated_info = None - access_token_id = None - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -class ProjectScopedPayload(BasePayload): - version = 2 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['project_id'] - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id) - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - (is_stored_as_bytes, project_id) = payload[2] - if is_stored_as_bytes: - project_id = cls.convert_uuid_bytes_to_hex(project_id) - expires_at_str = cls._convert_float_to_time_string(payload[3]) - audit_ids = list(map(provider.base64_encode, payload[4])) - domain_id = None - trust_id = None - federated_info = None - access_token_id = None - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -class TrustScopedPayload(BasePayload): - version = 3 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['trust_id'] - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id) - b_trust_id = cls.convert_uuid_hex_to_bytes(trust_id) - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - - return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids, - b_trust_id) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - (is_stored_as_bytes, project_id) = payload[2] - if is_stored_as_bytes: - project_id = cls.convert_uuid_bytes_to_hex(project_id) - expires_at_str = cls._convert_float_to_time_string(payload[3]) - audit_ids = list(map(provider.base64_encode, payload[4])) - trust_id = cls.convert_uuid_bytes_to_hex(payload[5]) - domain_id = None - federated_info = None - access_token_id = None - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -class FederatedUnscopedPayload(BasePayload): - version = 4 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['federated_info'] - - @classmethod - def pack_group_id(cls, group_dict): - return cls.attempt_convert_uuid_hex_to_bytes(group_dict['id']) - - @classmethod - def unpack_group_id(cls, group_id_in_bytes): - (is_stored_as_bytes, group_id) = group_id_in_bytes - if is_stored_as_bytes: - group_id = cls.convert_uuid_bytes_to_hex(group_id) - return {'id': group_id} - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - b_group_ids = list(map(cls.pack_group_id, - federated_info['group_ids'])) - b_idp_id = cls.attempt_convert_uuid_hex_to_bytes( - federated_info['idp_id']) - protocol_id = federated_info['protocol_id'] - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - - return (b_user_id, methods, b_group_ids, b_idp_id, protocol_id, - expires_at_int, b_audit_ids) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - group_ids = list(map(cls.unpack_group_id, payload[2])) - (is_stored_as_bytes, idp_id) = payload[3] - if is_stored_as_bytes: - idp_id = cls.convert_uuid_bytes_to_hex(idp_id) - protocol_id = payload[4] - expires_at_str = cls._convert_float_to_time_string(payload[5]) - audit_ids = list(map(provider.base64_encode, payload[6])) - federated_info = dict(group_ids=group_ids, idp_id=idp_id, - protocol_id=protocol_id) - project_id = None - domain_id = None - trust_id = None - access_token_id = None - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -class FederatedScopedPayload(FederatedUnscopedPayload): - version = None - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - b_scope_id = cls.attempt_convert_uuid_hex_to_bytes( - project_id or domain_id) - b_group_ids = list(map(cls.pack_group_id, - federated_info['group_ids'])) - b_idp_id = cls.attempt_convert_uuid_hex_to_bytes( - federated_info['idp_id']) - protocol_id = federated_info['protocol_id'] - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - - return (b_user_id, methods, b_scope_id, b_group_ids, b_idp_id, - protocol_id, expires_at_int, b_audit_ids) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - (is_stored_as_bytes, scope_id) = payload[2] - if is_stored_as_bytes: - scope_id = cls.convert_uuid_bytes_to_hex(scope_id) - project_id = ( - scope_id - if cls.version == FederatedProjectScopedPayload.version else None) - domain_id = ( - scope_id - if cls.version == FederatedDomainScopedPayload.version else None) - group_ids = list(map(cls.unpack_group_id, payload[3])) - (is_stored_as_bytes, idp_id) = payload[4] - if is_stored_as_bytes: - idp_id = cls.convert_uuid_bytes_to_hex(idp_id) - protocol_id = payload[5] - expires_at_str = cls._convert_float_to_time_string(payload[6]) - audit_ids = list(map(provider.base64_encode, payload[7])) - federated_info = dict(idp_id=idp_id, protocol_id=protocol_id, - group_ids=group_ids) - trust_id = None - access_token_id = None - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -class FederatedProjectScopedPayload(FederatedScopedPayload): - version = 5 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['project_id'] and kwargs['federated_info'] - - -class FederatedDomainScopedPayload(FederatedScopedPayload): - version = 6 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['domain_id'] and kwargs['federated_info'] - - -class OauthScopedPayload(BasePayload): - version = 7 - - @classmethod - def create_arguments_apply(cls, **kwargs): - return kwargs['access_token_id'] - - @classmethod - def assemble(cls, user_id, methods, project_id, domain_id, expires_at, - audit_ids, trust_id, federated_info, access_token_id): - b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) - methods = auth_plugins.convert_method_list_to_integer(methods) - b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id) - expires_at_int = cls._convert_time_string_to_float(expires_at) - b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, - audit_ids)) - b_access_token_id = cls.attempt_convert_uuid_hex_to_bytes( - access_token_id) - return (b_user_id, methods, b_project_id, b_access_token_id, - expires_at_int, b_audit_ids) - - @classmethod - def disassemble(cls, payload): - (is_stored_as_bytes, user_id) = payload[0] - if is_stored_as_bytes: - user_id = cls.convert_uuid_bytes_to_hex(user_id) - methods = auth_plugins.convert_integer_to_method_list(payload[1]) - (is_stored_as_bytes, project_id) = payload[2] - if is_stored_as_bytes: - project_id = cls.convert_uuid_bytes_to_hex(project_id) - (is_stored_as_bytes, access_token_id) = payload[3] - if is_stored_as_bytes: - access_token_id = cls.convert_uuid_bytes_to_hex(access_token_id) - expires_at_str = cls._convert_float_to_time_string(payload[4]) - audit_ids = list(map(provider.base64_encode, payload[5])) - domain_id = None - trust_id = None - federated_info = None - - return (user_id, methods, project_id, domain_id, expires_at_str, - audit_ids, trust_id, federated_info, access_token_id) - - -# For now, the order of the classes in the following list is important. This -# is because the way they test that the payload applies to them in -# the create_arguments_apply method requires that the previous ones rejected -# the payload arguments. For example, UnscopedPayload must be last since it's -# the catch-all after all the other payloads have been checked. -# TODO(blk-u): Clean up the create_arguments_apply methods so that they don't -# depend on the previous classes then these can be in any order. -PAYLOAD_CLASSES = [ - OauthScopedPayload, - TrustScopedPayload, - FederatedProjectScopedPayload, - FederatedDomainScopedPayload, - FederatedUnscopedPayload, - ProjectScopedPayload, - DomainScopedPayload, - UnscopedPayload, -] diff --git a/keystone-moon/keystone/token/providers/fernet/utils.py b/keystone-moon/keystone/token/providers/fernet/utils.py deleted file mode 100644 index 1c3552d4..00000000 --- a/keystone-moon/keystone/token/providers/fernet/utils.py +++ /dev/null @@ -1,270 +0,0 @@ -# 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 os -import stat - -from cryptography import fernet -from oslo_config import cfg -from oslo_log import log - -from keystone.i18n import _LE, _LW, _LI - - -LOG = log.getLogger(__name__) - -CONF = cfg.CONF - - -def validate_key_repository(requires_write=False): - """Validate permissions on the key repository directory.""" - # NOTE(lbragstad): We shouldn't need to check if the directory was passed - # in as None because we don't set allow_no_values to True. - - # ensure current user has sufficient access to the key repository - is_valid = (os.access(CONF.fernet_tokens.key_repository, os.R_OK) and - os.access(CONF.fernet_tokens.key_repository, os.X_OK)) - if requires_write: - is_valid = (is_valid and - os.access(CONF.fernet_tokens.key_repository, os.W_OK)) - - if not is_valid: - LOG.error( - _LE('Either [fernet_tokens] key_repository does not exist or ' - 'Keystone does not have sufficient permission to access it: ' - '%s'), CONF.fernet_tokens.key_repository) - else: - # ensure the key repository isn't world-readable - stat_info = os.stat(CONF.fernet_tokens.key_repository) - if(stat_info.st_mode & stat.S_IROTH or - stat_info.st_mode & stat.S_IXOTH): - LOG.warning(_LW( - '[fernet_tokens] key_repository is world readable: %s'), - CONF.fernet_tokens.key_repository) - - return is_valid - - -def _convert_to_integers(id_value): - """Cast user and group system identifiers to integers.""" - # NOTE(lbragstad) os.chown() will raise a TypeError here if - # keystone_user_id and keystone_group_id are not integers. Let's - # cast them to integers if we can because it's possible to pass non-integer - # values into the fernet_setup utility. - try: - id_int = int(id_value) - except ValueError as e: - msg = _LE('Unable to convert Keystone user or group ID. Error: %s') - LOG.error(msg, e) - raise - - return id_int - - -def create_key_directory(keystone_user_id=None, keystone_group_id=None): - """If the configured key directory does not exist, attempt to create it.""" - if not os.access(CONF.fernet_tokens.key_repository, os.F_OK): - LOG.info(_LI( - '[fernet_tokens] key_repository does not appear to exist; ' - 'attempting to create it')) - - try: - os.makedirs(CONF.fernet_tokens.key_repository, 0o700) - except OSError: - LOG.error(_LE( - 'Failed to create [fernet_tokens] key_repository: either it ' - 'already exists or you don\'t have sufficient permissions to ' - 'create it')) - - if keystone_user_id and keystone_group_id: - os.chown( - CONF.fernet_tokens.key_repository, - keystone_user_id, - keystone_group_id) - elif keystone_user_id or keystone_group_id: - LOG.warning(_LW( - 'Unable to change the ownership of [fernet_tokens] ' - 'key_repository without a keystone user ID and keystone group ' - 'ID both being provided: %s') % - CONF.fernet_tokens.key_repository) - - -def _create_new_key(keystone_user_id, keystone_group_id): - """Securely create a new encryption key. - - Create a new key that is readable by the Keystone group and Keystone user. - """ - key = fernet.Fernet.generate_key() # key is bytes - - # This ensures the key created is not world-readable - old_umask = os.umask(0o177) - if keystone_user_id and keystone_group_id: - old_egid = os.getegid() - old_euid = os.geteuid() - os.setegid(keystone_group_id) - os.seteuid(keystone_user_id) - elif keystone_user_id or keystone_group_id: - LOG.warning(_LW( - 'Unable to change the ownership of the new key without a keystone ' - 'user ID and keystone group ID both being provided: %s') % - CONF.fernet_tokens.key_repository) - # Determine the file name of the new key - key_file = os.path.join(CONF.fernet_tokens.key_repository, '0') - try: - with open(key_file, 'w') as f: - f.write(key.decode('utf-8')) # convert key to str for the file. - finally: - # After writing the key, set the umask back to it's original value. Do - # the same with group and user identifiers if a Keystone group or user - # was supplied. - os.umask(old_umask) - if keystone_user_id and keystone_group_id: - os.seteuid(old_euid) - os.setegid(old_egid) - - LOG.info(_LI('Created a new key: %s'), key_file) - - -def initialize_key_repository(keystone_user_id=None, keystone_group_id=None): - """Create a key repository and bootstrap it with a key. - - :param keystone_user_id: User ID of the Keystone user. - :param keystone_group_id: Group ID of the Keystone user. - - """ - # make sure we have work to do before proceeding - if os.access(os.path.join(CONF.fernet_tokens.key_repository, '0'), - os.F_OK): - LOG.info(_LI('Key repository is already initialized; aborting.')) - return - - # bootstrap an existing key - _create_new_key(keystone_user_id, keystone_group_id) - - # ensure that we end up with a primary and secondary key - rotate_keys(keystone_user_id, keystone_group_id) - - -def rotate_keys(keystone_user_id=None, keystone_group_id=None): - """Create a new primary key and revoke excess active keys. - - :param keystone_user_id: User ID of the Keystone user. - :param keystone_group_id: Group ID of the Keystone user. - - Key rotation utilizes the following behaviors: - - - The highest key number is used as the primary key (used for encryption). - - All keys can be used for decryption. - - New keys are always created as key "0," which serves as a placeholder - before promoting it to be the primary key. - - This strategy allows you to safely perform rotation on one node in a - cluster, before syncing the results of the rotation to all other nodes - (during both key rotation and synchronization, all nodes must recognize all - primary keys). - - """ - # read the list of key files - key_files = dict() - for filename in os.listdir(CONF.fernet_tokens.key_repository): - path = os.path.join(CONF.fernet_tokens.key_repository, str(filename)) - if os.path.isfile(path): - try: - key_id = int(filename) - except ValueError: # nosec : name isn't a number, ignore the file. - pass - else: - key_files[key_id] = path - - LOG.info(_LI('Starting key rotation with %(count)s key files: %(list)s'), { - 'count': len(key_files), - 'list': list(key_files.values())}) - - # determine the number of the new primary key - current_primary_key = max(key_files.keys()) - LOG.info(_LI('Current primary key is: %s'), current_primary_key) - new_primary_key = current_primary_key + 1 - LOG.info(_LI('Next primary key will be: %s'), new_primary_key) - - # promote the next primary key to be the primary - os.rename( - os.path.join(CONF.fernet_tokens.key_repository, '0'), - os.path.join(CONF.fernet_tokens.key_repository, str(new_primary_key))) - key_files.pop(0) - key_files[new_primary_key] = os.path.join( - CONF.fernet_tokens.key_repository, - str(new_primary_key)) - LOG.info(_LI('Promoted key 0 to be the primary: %s'), new_primary_key) - - # add a new key to the rotation, which will be the *next* primary - _create_new_key(keystone_user_id, keystone_group_id) - - max_active_keys = CONF.fernet_tokens.max_active_keys - # check for bad configuration - if max_active_keys < 1: - LOG.warning(_LW( - '[fernet_tokens] max_active_keys must be at least 1 to maintain a ' - 'primary key.')) - max_active_keys = 1 - - # purge excess keys - - # Note that key_files doesn't contain the new active key that was created, - # only the old active keys. - keys = sorted(key_files.keys(), reverse=True) - while len(keys) > (max_active_keys - 1): - index_to_purge = keys.pop() - key_to_purge = key_files[index_to_purge] - LOG.info(_LI('Excess key to purge: %s'), key_to_purge) - os.remove(key_to_purge) - - -def load_keys(): - """Load keys from disk into a list. - - The first key in the list is the primary key used for encryption. All - other keys are active secondary keys that can be used for decrypting - tokens. - - """ - if not validate_key_repository(): - return [] - - # build a dictionary of key_number:encryption_key pairs - keys = dict() - for filename in os.listdir(CONF.fernet_tokens.key_repository): - path = os.path.join(CONF.fernet_tokens.key_repository, str(filename)) - if os.path.isfile(path): - with open(path, 'r') as key_file: - try: - key_id = int(filename) - except ValueError: # nosec : filename isn't a number, ignore - # this file since it's not a key. - pass - else: - keys[key_id] = key_file.read() - - if len(keys) != CONF.fernet_tokens.max_active_keys: - # If there haven't been enough key rotations to reach max_active_keys, - # or if the configured value of max_active_keys has changed since the - # last rotation, then reporting the discrepancy might be useful. Once - # the number of keys matches max_active_keys, this log entry is too - # repetitive to be useful. - LOG.info(_LI( - 'Loaded %(count)d encryption keys (max_active_keys=%(max)d) from: ' - '%(dir)s'), { - 'count': len(keys), - 'max': CONF.fernet_tokens.max_active_keys, - 'dir': CONF.fernet_tokens.key_repository}) - - # return the encryption_keys, sorted by key number, descending - return [keys[x] for x in sorted(keys.keys(), reverse=True)] diff --git a/keystone-moon/keystone/token/providers/pki.py b/keystone-moon/keystone/token/providers/pki.py deleted file mode 100644 index 6a5a2999..00000000 --- a/keystone-moon/keystone/token/providers/pki.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2013 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. - -"""Keystone PKI Token Provider""" - -from keystoneclient.common import cms -from oslo_config import cfg -from oslo_log import log -from oslo_log import versionutils -from oslo_serialization import jsonutils - -from keystone.common import environment -from keystone.common import utils -from keystone import exception -from keystone.i18n import _, _LE -from keystone.token.providers import common - - -CONF = cfg.CONF - -LOG = log.getLogger(__name__) - - -@versionutils.deprecated( - as_of=versionutils.deprecated.MITAKA, - what='the PKI token provider', - in_favor_of='the Fernet or UUID token providers') -class Provider(common.BaseProvider): - def _get_token_id(self, token_data): - try: - # force conversion to a string as the keystone client cms code - # produces unicode. This can be removed if the client returns - # str() - # TODO(ayoung): Make to a byte_str for Python3 - token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder) - token_id = str(cms.cms_sign_token(token_json, - CONF.signing.certfile, - CONF.signing.keyfile)) - return token_id - except environment.subprocess.CalledProcessError: - LOG.exception(_LE('Unable to sign token')) - raise exception.UnexpectedError(_( - 'Unable to sign token.')) - - @property - def _supports_bind_authentication(self): - """Return if the token provider supports bind authentication methods. - - :returns: True - """ - return True - - def needs_persistence(self): - """Should the token be written to a backend.""" - return True diff --git a/keystone-moon/keystone/token/providers/pkiz.py b/keystone-moon/keystone/token/providers/pkiz.py deleted file mode 100644 index 3e78d2e4..00000000 --- a/keystone-moon/keystone/token/providers/pkiz.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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. - -"""Keystone Compressed PKI Token Provider""" - -from keystoneclient.common import cms -from oslo_config import cfg -from oslo_log import log -from oslo_log import versionutils -from oslo_serialization import jsonutils - -from keystone.common import environment -from keystone.common import utils -from keystone import exception -from keystone.i18n import _ -from keystone.token.providers import common - - -CONF = cfg.CONF - -LOG = log.getLogger(__name__) -ERROR_MESSAGE = _('Unable to sign token.') - - -@versionutils.deprecated( - as_of=versionutils.deprecated.MITAKA, - what='the PKIZ token provider', - in_favor_of='the Fernet or UUID token providers') -class Provider(common.BaseProvider): - def _get_token_id(self, token_data): - try: - # force conversion to a string as the keystone client cms code - # produces unicode. This can be removed if the client returns - # str() - # TODO(ayoung): Make to a byte_str for Python3 - token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder) - token_id = str(cms.pkiz_sign(token_json, - CONF.signing.certfile, - CONF.signing.keyfile)) - return token_id - except environment.subprocess.CalledProcessError: - LOG.exception(ERROR_MESSAGE) - raise exception.UnexpectedError(ERROR_MESSAGE) - - @property - def _supports_bind_authentication(self): - """Return if the token provider supports bind authentication methods. - - :returns: True - """ - return True - - def needs_persistence(self): - """Should the token be written to a backend.""" - return True diff --git a/keystone-moon/keystone/token/providers/uuid.py b/keystone-moon/keystone/token/providers/uuid.py deleted file mode 100644 index f9a91617..00000000 --- a/keystone-moon/keystone/token/providers/uuid.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2013 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. - -"""Keystone UUID Token Provider""" - -from __future__ import absolute_import - -import uuid - -from keystone.token.providers import common - - -class Provider(common.BaseProvider): - def __init__(self, *args, **kwargs): - super(Provider, self).__init__(*args, **kwargs) - - def _get_token_id(self, token_data): - return uuid.uuid4().hex - - @property - def _supports_bind_authentication(self): - """Return if the token provider supports bind authentication methods. - - :returns: True - """ - return True - - def needs_persistence(self): - """Should the token be written to a backend.""" - return True diff --git a/keystone-moon/keystone/token/routers.py b/keystone-moon/keystone/token/routers.py deleted file mode 100644 index bcd40ee4..00000000 --- a/keystone-moon/keystone/token/routers.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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. -from keystone.common import wsgi -from keystone.token import controllers - - -class Router(wsgi.ComposableRouter): - def add_routes(self, mapper): - token_controller = controllers.Auth() - mapper.connect('/tokens', - controller=token_controller, - action='authenticate', - conditions=dict(method=['POST'])) - mapper.connect('/tokens/revoked', - controller=token_controller, - action='revocation_list', - conditions=dict(method=['GET'])) - mapper.connect('/tokens/{token_id}', - controller=token_controller, - action='validate_token', - conditions=dict(method=['GET'])) - # NOTE(morganfainberg): For policy enforcement reasons, the - # ``validate_token_head`` method is still used for HEAD requests. - # The controller method makes the same call as the validate_token - # call and lets wsgi.render_response remove the body data. - mapper.connect('/tokens/{token_id}', - controller=token_controller, - action='validate_token_head', - conditions=dict(method=['HEAD'])) - mapper.connect('/tokens/{token_id}', - controller=token_controller, - action='delete_token', - conditions=dict(method=['DELETE'])) - mapper.connect('/tokens/{token_id}/endpoints', - controller=token_controller, - action='endpoints', - conditions=dict(method=['GET'])) - - # Certificates used to verify auth tokens - mapper.connect('/certificates/ca', - controller=token_controller, - action='ca_cert', - conditions=dict(method=['GET'])) - - mapper.connect('/certificates/signing', - controller=token_controller, - action='signing_cert', - conditions=dict(method=['GET'])) diff --git a/keystone-moon/keystone/token/utils.py b/keystone-moon/keystone/token/utils.py deleted file mode 100644 index 96a09246..00000000 --- a/keystone-moon/keystone/token/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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. - -from keystoneclient.common import cms -from oslo_config import cfg - - -def generate_unique_id(token_id): - """Return a unique ID for a token. - - The returned value is useful as the primary key of a database table, - memcache store, or other lookup table. - - :returns: Given a PKI token, returns it's hashed value. Otherwise, - returns the passed-in value (such as a UUID token ID or an - existing hash). - """ - return cms.cms_hash_token(token_id, mode=cfg.CONF.token.hash_algorithm) |