aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/token
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/token')
-rw-r--r--keystone-moon/keystone/token/__init__.py17
-rw-r--r--keystone-moon/keystone/token/_simple_cert.py91
-rw-r--r--keystone-moon/keystone/token/controllers.py529
-rw-r--r--keystone-moon/keystone/token/persistence/__init__.py16
-rw-r--r--keystone-moon/keystone/token/persistence/backends/__init__.py0
-rw-r--r--keystone-moon/keystone/token/persistence/backends/kvs.py367
-rw-r--r--keystone-moon/keystone/token/persistence/backends/memcache.py39
-rw-r--r--keystone-moon/keystone/token/persistence/backends/memcache_pool.py34
-rw-r--r--keystone-moon/keystone/token/persistence/backends/sql.py286
-rw-r--r--keystone-moon/keystone/token/persistence/core.py357
-rw-r--r--keystone-moon/keystone/token/provider.py637
-rw-r--r--keystone-moon/keystone/token/providers/__init__.py0
-rw-r--r--keystone-moon/keystone/token/providers/common.py808
-rw-r--r--keystone-moon/keystone/token/providers/fernet/__init__.py13
-rw-r--r--keystone-moon/keystone/token/providers/fernet/core.py211
-rw-r--r--keystone-moon/keystone/token/providers/fernet/token_formatters.py677
-rw-r--r--keystone-moon/keystone/token/providers/fernet/utils.py270
-rw-r--r--keystone-moon/keystone/token/providers/pki.py66
-rw-r--r--keystone-moon/keystone/token/providers/pkiz.py64
-rw-r--r--keystone-moon/keystone/token/providers/uuid.py41
-rw-r--r--keystone-moon/keystone/token/routers.py59
-rw-r--r--keystone-moon/keystone/token/utils.py27
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)