diff options
Diffstat (limited to 'keystone/token/providers/uuid.py')
-rw-r--r-- | keystone/token/providers/uuid.py | 250 |
1 files changed, 233 insertions, 17 deletions
diff --git a/keystone/token/providers/uuid.py b/keystone/token/providers/uuid.py index e1bd0b3b..413c7479 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 @@ -37,6 +36,108 @@ 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.""" def __init__(self): @@ -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) |