From ee27d6eef62d201c99694d0f788ea2a96c6669a4 Mon Sep 17 00:00:00 2001 From: Guang Yee Date: Mon, 24 Jun 2013 23:37:59 -0700 Subject: [PATCH] Implements Pluggable V2 Token Provider This patch implemented V2 token provider. Abstract token provider backend to make token provider pluggable. It enables deployers to customize token management to add their own capabilities. Token provider is responsible for issuing, checking, validating, and revoking tokens. Note the distinction between token 'driver' and 'provider'. Token 'driver' simply provides token CRUD. It does not issue or interpret tokens. Token provider is specified by the 'provider' property in the '[token]' section of the Keystone configuration file. Change-Id: Ic418ec433bd9e3f2f70fa31c90e570e32c1ca687 --- keystone/common/controller.py | 2 +- keystone/contrib/ec2/core.py | 23 ++- keystone/token/controllers.py | 214 ++------------------------ keystone/token/provider.py | 3 + keystone/token/providers/uuid.py | 250 ++++++++++++++++++++++++++++--- tests/test_keystoneclient.py | 24 +++ 6 files changed, 287 insertions(+), 229 deletions(-) diff --git a/keystone/common/controller.py b/keystone/common/controller.py index 0ad1efa3e0..0fb91cf18f 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -26,7 +26,7 @@ def _build_policy_check_credentials(self, action, context, kwargs): raise exception.Unauthorized() creds = {} - if 'token_data' in token_ref: + if 'token_data' in token_ref and 'token' in token_ref['token_data']: #V3 Tokens token_data = token_ref['token_data']['token'] try: diff --git a/keystone/contrib/ec2/core.py b/keystone/contrib/ec2/core.py index 5254b53fc8..fed7ee087c 100644 --- a/keystone/contrib/ec2/core.py +++ b/keystone/contrib/ec2/core.py @@ -97,7 +97,7 @@ class Ec2Extension(wsgi.ExtensionRouter): conditions=dict(method=['DELETE'])) -@dependency.requires('catalog_api', 'ec2_api') +@dependency.requires('catalog_api', 'ec2_api', 'token_provider_api') class Ec2Controller(controller.V2Controller): def check_signature(self, creds_ref, credentials): signer = ec2_utils.Ec2Signer(creds_ref['secret']) @@ -172,17 +172,16 @@ class Ec2Controller(controller.V2Controller): tenant_id=tenant_ref['id'], metadata=metadata_ref) - token_ref = self.token_api.create_token( - token_id, dict(id=token_id, - user=user_ref, - tenant=tenant_ref, - metadata=metadata_ref)) - - # TODO(termie): i don't think the ec2 middleware currently expects a - # full return, but it contains a note saying that it - # would be better to expect a full return - return token.controllers.Auth.format_authenticate( - token_ref, roles_ref, catalog_ref) + auth_token_data = dict(user=user_ref, + tenant=tenant_ref, + metadata=metadata_ref, + id='placeholder') + (token_id, token_data) = self.token_provider_api.issue_token( + version=token.provider.V2, + token_ref=auth_token_data, + roles_ref=roles_ref, + catalog_ref=catalog_ref) + return token_data def create_credential(self, context, user_id, tenant_id): """Create a secret/access pair for use with ec2 style auth. diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py index 1ada05ea03..4914d3055c 100644 --- a/keystone/token/controllers.py +++ b/keystone/token/controllers.py @@ -1,16 +1,16 @@ import json -import sys -import uuid from keystone.common import cms from keystone.common import controller -from keystone.common import environment +from keystone.common import dependency from keystone.common import logging from keystone.common import utils from keystone import config from keystone import exception from keystone.openstack.common import timeutils from keystone.token import core +from keystone.token import provider as token_provider + CONF = config.CONF LOG = logging.getLogger(__name__) @@ -22,6 +22,7 @@ class ExternalAuthNotApplicable(Exception): pass +@dependency.requires('token_provider_api') class Auth(controller.V2Controller): def ca_cert(self, context, auth=None): ca_file = open(CONF.signing.ca_certs, 'r') @@ -79,7 +80,6 @@ class Auth(controller.V2Controller): user_ref, tenant_ref, metadata_ref, expiry = auth_info core.validate_auth_info(self, user_ref, tenant_ref) - trust_id = metadata_ref.get('trust_id') user_ref = self._filter_domain_id(user_ref) if tenant_ref: tenant_ref = self._filter_domain_id(tenant_ref) @@ -103,46 +103,11 @@ class Auth(controller.V2Controller): role_ref = self.identity_api.get_role(role_id) roles_ref.append(dict(name=role_ref['name'])) - token_data = Auth.format_token(auth_token_data, roles_ref) - - service_catalog = Auth.format_catalog(catalog_ref) - token_data['access']['serviceCatalog'] = service_catalog - - if CONF.signing.token_format == 'UUID': - token_id = uuid.uuid4().hex - elif CONF.signing.token_format == 'PKI': - try: - token_id = cms.cms_sign_token(json.dumps(token_data), - CONF.signing.certfile, - CONF.signing.keyfile) - except environment.subprocess.CalledProcessError: - raise exception.UnexpectedError(_( - 'Unable to sign token.')) - else: - raise exception.UnexpectedError(_( - 'Invalid value for token_format: %s.' - ' Allowed values are PKI or UUID.') % - CONF.signing.token_format) - try: - self.token_api.create_token( - token_id, dict(key=token_id, - id=token_id, - expires=auth_token_data['expires'], - user=user_ref, - tenant=tenant_ref, - metadata=metadata_ref, - trust_id=trust_id)) - 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.token_api.get_token(token_id) - except exception.TokenNotFound: - raise exc_info[0], exc_info[1], exc_info[2] - - token_data['access']['token']['id'] = token_id - + (token_id, token_data) = self.token_provider_api.issue_token( + version=token_provider.V2, + token_ref=auth_token_data, + roles_ref=roles_ref, + catalog_ref=catalog_ref) return token_data def _authenticate_token(self, context, auth): @@ -416,45 +381,6 @@ class Auth(controller.V2Controller): _('Token does not belong to specified tenant.')) return data - def _assert_default_domain(self, token_ref): - """Make sure we are operating on default domain only.""" - if token_ref.get('token_data'): - # this is a V3 token - msg = _('Non-default domain is not supported') - # user in a non-default is prohibited - if (token_ref['token_data']['token']['user']['domain']['id'] != - DEFAULT_DOMAIN_ID): - raise exception.Unauthorized(msg) - # domain scoping is prohibited - if token_ref['token_data']['token'].get('domain'): - raise exception.Unauthorized( - _('Domain scoped token is not supported')) - # project in non-default domain is prohibited - if token_ref['token_data']['token'].get('project'): - project = token_ref['token_data']['token']['project'] - project_domain_id = project['domain']['id'] - # scoped to project in non-default domain is prohibited - if project_domain_id != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - # 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'] != 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'] != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - project_ref = self.identity_api.get_project( - trust_ref['project_id']) - if project_ref['domain_id'] != DEFAULT_DOMAIN_ID: - raise exception.Unauthorized(msg) - @controller.protected def validate_token_head(self, context, token_id): """Check that a token is valid. @@ -465,9 +391,9 @@ class Auth(controller.V2Controller): """ belongs_to = context['query_string'].get('belongsTo') - token_ref = self._get_token_ref(token_id, belongs_to) - assert token_ref - self._assert_default_domain(token_ref) + self.token_provider_api.check_token(token_id, + belongs_to=belongs_to, + version=token_provider.V2) @controller.protected def validate_token(self, context, token_id): @@ -479,26 +405,9 @@ class Auth(controller.V2Controller): """ belongs_to = context['query_string'].get('belongsTo') - token_ref = self._get_token_ref(token_id, belongs_to) - self._assert_default_domain(token_ref) - - # TODO(termie): optimize this call at some point and put it into the - # the return for metadata - # fill out the roles in the metadata - metadata_ref = token_ref['metadata'] - roles_ref = [] - for role_id in metadata_ref.get('roles', []): - roles_ref.append(self.identity_api.get_role(role_id)) - - # Get a service catalog if possible - # This is needed for on-behalf-of requests - catalog_ref = None - if token_ref.get('tenant'): - catalog_ref = self.catalog_api.get_catalog( - user_id=token_ref['user']['id'], - tenant_id=token_ref['tenant']['id'], - metadata=metadata_ref) - return Auth.format_token(token_ref, roles_ref, catalog_ref) + return self.token_provider_api.validate_token( + token_id, belongs_to=belongs_to, + version=token_provider.V2) def delete_token(self, context, token_id): """Delete a token, effectively invalidating it for authz.""" @@ -537,99 +446,6 @@ class Auth(controller.V2Controller): return Auth.format_endpoint_list(catalog_ref) - @classmethod - def format_authenticate(cls, token_ref, roles_ref, catalog_ref): - o = Auth.format_token(token_ref, roles_ref) - o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref) - return o - - @classmethod - def format_token(cls, token_ref, roles_ref, catalog_ref=None): - user_ref = token_ref['user'] - metadata_ref = token_ref['metadata'] - expires = token_ref['expires'] - if expires is not None: - if not isinstance(expires, unicode): - expires = timeutils.isotime(expires) - o = {'access': {'token': {'id': token_ref['id'], - 'expires': expires, - 'issued_at': timeutils.strtime() - }, - 'user': {'id': user_ref['id'], - 'name': user_ref['name'], - 'username': user_ref['name'], - 'roles': roles_ref, - 'roles_links': metadata_ref.get('roles_links', - []) - } - } - } - 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'] = Auth.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_id' in metadata_ref: - o['access']['trust'] = {'trustee_user_id': - metadata_ref['trustee_user_id'], - 'id': metadata_ref['trust_id'] - } - 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.iteritems(): - for service, service_ref in region_ref.iteritems(): - 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 services.values() - @classmethod def format_endpoint_list(cls, catalog_ref): """Formats a list of endpoints according to Identity API v2. diff --git a/keystone/token/provider.py b/keystone/token/provider.py index b5ad72faf5..3bb14e010d 100644 --- a/keystone/token/provider.py +++ b/keystone/token/provider.py @@ -81,6 +81,9 @@ class Provider(object): domain-scoped token; and 'auth_context' from the authentication plugins. + For V2 tokens, 'token_ref' must be present in kwargs. + Optionally, kwargs may contain 'roles_ref' and 'catalog_ref'. + :param context: request context :type context: dictionary :param version: version of the token to be issued diff --git a/keystone/token/providers/uuid.py b/keystone/token/providers/uuid.py index e1bd0b3b51..413c74790e 100644 --- a/keystone/token/providers/uuid.py +++ b/keystone/token/providers/uuid.py @@ -27,7 +27,6 @@ from keystone import config from keystone import exception from keystone.openstack.common import timeutils from keystone import token -from keystone.token import provider as token_provider from keystone import trust @@ -36,6 +35,108 @@ CONF = config.CONF DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id +@dependency.requires('catalog_api', 'identity_api') +class V2TokenDataHelper(object): + """Creates V2 token data.""" + @classmethod + def format_token(cls, token_ref, roles_ref, catalog_ref=None): + user_ref = token_ref['user'] + metadata_ref = token_ref['metadata'] + expires = token_ref.get('expires', token.default_expire_time()) + if expires is not None: + if not isinstance(expires, unicode): + expires = timeutils.isotime(expires) + o = {'access': {'token': {'id': token_ref['id'], + 'expires': expires, + 'issued_at': timeutils.strtime() + }, + 'user': {'id': user_ref['id'], + 'name': user_ref['name'], + 'username': user_ref['name'], + 'roles': roles_ref, + 'roles_links': metadata_ref.get('roles_links', + []) + } + } + } + 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_id' in metadata_ref: + o['access']['trust'] = {'trustee_user_id': + metadata_ref['trustee_user_id'], + 'id': metadata_ref['trust_id'] + } + 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.iteritems(): + for service, service_ref in region_ref.iteritems(): + 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 services.values() + + @classmethod + def get_token_data(cls, **kwargs): + if 'token_ref' not in kwargs: + raise ValueError('Require token_ref to create V2 token data') + token_ref = kwargs.get('token_ref') + roles_ref = kwargs.get('roles_ref', []) + catalog_ref = kwargs.get('catalog_ref') + return V2TokenDataHelper.format_token( + token_ref, roles_ref, catalog_ref) + + @dependency.requires('catalog_api', 'identity_api') class V3TokenDataHelper(object): """Token data helper.""" @@ -207,25 +308,56 @@ class V3TokenDataHelper(object): return {'token': token_data} -@dependency.requires('token_api', 'identity_api') -class Provider(token_provider.Provider): +@dependency.requires('token_api', 'identity_api', 'catalog_api') +class Provider(token.provider.Provider): def __init__(self, *args, **kwargs): super(Provider, self).__init__(*args, **kwargs) if CONF.trust.enabled: self.trust_api = trust.Manager() 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 'access' in token_data: - return token_provider.V2 + return token.provider.V2 if 'token' in token_data and 'methods' in token_data['token']: - return token_provider.V3 - raise token_provider.UnsupportedTokenVersionException() + return token.provider.V3 + raise token.provider.UnsupportedTokenVersionException() def _get_token_id(self, token_data): return uuid.uuid4().hex + def _issue_v2_token(self, **kwargs): + token_data = self.v2_token_data_helper.get_token_data(**kwargs) + token_id = self._get_token_id(token_data) + token_data['access']['token']['id'] = token_id + try: + expiry = token_data['access']['token']['expires'] + token_ref = kwargs.get('token_ref') + if isinstance(expiry, basestring): + expiry = timeutils.normalize_time( + timeutils.parse_isotime(expiry)) + data = dict(key=token_id, + id=token_id, + expires=expiry, + user=token_ref['user'], + tenant=token_ref['tenant'], + metadata=token_ref['metadata'], + token_data=token_data, + trust_id=token_ref['metadata'].get('trust_id')) + self.token_api.create_token(token_id, 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.token_api.get_token(token_id) + except exception.TokenNotFound: + raise exc_info[0], exc_info[1], exc_info[2] + + return (token_id, token_data) + def _issue_v3_token(self, **kwargs): user_id = kwargs.get('user_id') method_names = kwargs.get('method_names') @@ -287,21 +419,104 @@ class Provider(token_provider.Provider): return (token_id, token_data) def issue_token(self, version='v3.0', **kwargs): - if version == token_provider.V3: + if version == token.provider.V3: return self._issue_v3_token(**kwargs) - raise token_provider.UnsupportedTokenVersionException + elif version == token.provider.V2: + return self._issue_v2_token(**kwargs) + raise token.provider.UnsupportedTokenVersionException def _verify_token(self, token_id, belongs_to=None): """Verify the given token and return the token_ref.""" token_ref = self.token_api.get_token(token_id=token_id) assert token_ref if belongs_to: - assert token_ref['tenant']['id'] == belongs_to + assert (token_ref['tenant'] and + token_ref['tenant']['id'] == belongs_to) return token_ref def revoke_token(self, token_id): self.token_api.delete_token(token_id=token_id) + 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') + # user in a non-default is prohibited + if (token_ref['token_data']['token']['user']['domain']['id'] != + DEFAULT_DOMAIN_ID): + raise exception.Unauthorized(msg) + # domain scoping is prohibited + if token_ref['token_data']['token'].get('domain'): + raise exception.Unauthorized( + _('Domain scoped token is not supported')) + # project in non-default domain is prohibited + if token_ref['token_data']['token'].get('project'): + project = token_ref['token_data']['token']['project'] + project_domain_id = project['domain']['id'] + # scoped to project in non-default domain is prohibited + if project_domain_id != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + # 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'] != 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'] != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + project_ref = self.identity_api.get_project( + trust_ref['project_id']) + if project_ref['domain_id'] != DEFAULT_DOMAIN_ID: + raise exception.Unauthorized(msg) + + def _validate_v2_token(self, token_id, belongs_to=None, **kwargs): + try: + token_ref = self._verify_token(token_id, belongs_to=belongs_to) + 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 (not token_data or + self.get_token_version(token_data) != + token.provider.V2): + # token is created by old v2 logic + metadata_ref = token_ref['metadata'] + role_refs = [] + for role_id in metadata_ref.get('roles', []): + role_refs.append(self.identity_api.get_role(role_id)) + + # Get a service catalog if possible + # This is needed for on-behalf-of requests + catalog_ref = None + if token_ref.get('tenant'): + catalog_ref = self.catalog_api.get_catalog( + token_ref['user']['id'], + token_ref['tenant']['id'], + metadata=metadata_ref) + token_data = self.v2_token_data_helper.get_token_data( + token_ref=token_ref, + roles_ref=role_refs, + catalog_ref=catalog_ref) + return token_data + except AssertionError as e: + LOG.exception(_('Failed to validate token')) + raise exception.Unauthorized(e) + def _validate_v3_token(self, token_id): token_ref = self._verify_token(token_id) # FIXME(gyee): performance or correctness? Should we return the @@ -327,12 +542,14 @@ class Provider(token_provider.Provider): expires=token_ref['expires']) return token_data - def validate_token(self, token_id, belongs_to=None, - version='v3.0'): + def validate_token(self, token_id, belongs_to=None, version='v3.0'): try: - if version == token_provider.V3: + if version == token.provider.V3: return self._validate_v3_token(token_id) - raise token_provider.UnsupportedTokenVersionException() + elif version == token.provider.V2: + return self._validate_v2_token(token_id, + belongs_to=belongs_to) + raise token.provider.UnsupportedTokenVersionException() except exception.TokenNotFound as e: LOG.exception(_('Failed to verify token')) raise exception.Unauthorized(e) @@ -340,10 +557,9 @@ class Provider(token_provider.Provider): def check_token(self, token_id, belongs_to=None, version='v3.0', **kwargs): try: - if version == token_provider.V3: - self._verify_token(token_id) - else: - raise token_provider.UnsupportedTokenVersionException() + token_ref = self._verify_token(token_id, belongs_to=belongs_to) + if version == token.provider.V2: + self._assert_default_domain(token_ref) except exception.TokenNotFound as e: LOG.exception(_('Failed to verify token')) raise exception.Unauthorized(e) diff --git a/tests/test_keystoneclient.py b/tests/test_keystoneclient.py index d78e885a2d..5c0d2f5bce 100644 --- a/tests/test_keystoneclient.py +++ b/tests/test_keystoneclient.py @@ -20,6 +20,7 @@ import webob import nose.exc from keystone import test +from keystone import token from keystone import config from keystone.openstack.common import jsonutils @@ -42,6 +43,7 @@ class CompatTestCase(test.TestCase): self.clear_module('keystoneclient') self.load_backends() + self.token_provider_api = token.provider.Manager() self.load_fixtures(default_fixtures) self.public_server = self.serveapp('keystone', name='main') @@ -839,6 +841,28 @@ class KcMasterTestCase(CompatTestCase, KeystoneClientTests): def get_checkout(self): return KEYSTONECLIENT_REPO, 'master' + def test_ec2_auth(self): + client = self.get_client() + cred = client.ec2.create(user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id']) + + from keystoneclient.contrib.ec2 import utils as ec2_utils + signer = ec2_utils.Ec2Signer(cred.secret) + credentials = {'params': {'SignatureVersion': '2'}, + 'access': cred.access, + 'verb': 'GET', + 'host': 'localhost', + 'path': '/thisisgoingtowork'} + signature = signer.generate(credentials) + credentials['signature'] = signature + url = '%s/ec2tokens' % (client.auth_url) + (resp, token) = client.request(url=url, + method='POST', + body={'credentials': credentials}) + # make sure we have a v2 token + self.assertEqual(resp.status_code, 200) + self.assertIn('access', token) + def test_tenant_add_and_remove_user(self): client = self.get_client(admin=True) client.roles.add_user_role(tenant=self.tenant_bar['id'],