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
This commit is contained in:
parent
c238ace309
commit
ee27d6eef6
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'],
|
||||
|
|
Loading…
Reference in New Issue