Merge "Remove all v2.0 APIs except the ec2tokens API"
This commit is contained in:
commit
bcdbed82ed
|
@ -20,7 +20,6 @@ import functools
|
|||
from oslo_log import log
|
||||
|
||||
from keystone.assignment import schema
|
||||
from keystone.common import authorization
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import validation
|
||||
|
@ -35,32 +34,6 @@ LOG = log.getLogger(__name__)
|
|||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class TenantAssignment(controller.V2Controller):
|
||||
"""The V2 Project APIs that are processing assignments."""
|
||||
|
||||
@controller.v2_auth_deprecated
|
||||
def get_projects_for_token(self, request, **kw):
|
||||
"""Get valid tenants for token based on token used to authenticate.
|
||||
|
||||
Pulls the token from the context, validates it and gets the valid
|
||||
tenants for the user in the token.
|
||||
|
||||
Doesn't care about token scopedness.
|
||||
|
||||
"""
|
||||
token_ref = authorization.get_token_ref(request.context_dict)
|
||||
|
||||
tenant_refs = (
|
||||
PROVIDERS.assignment_api.list_projects_for_user(token_ref.user_id))
|
||||
tenant_refs = [self.v3_to_v2_project(ref) for ref in tenant_refs
|
||||
if ref['domain_id'] == CONF.identity.default_domain_id]
|
||||
params = {
|
||||
'limit': request.params.get('limit'),
|
||||
'marker': request.params.get('marker'),
|
||||
}
|
||||
return self.format_project_list(tenant_refs, **params)
|
||||
|
||||
|
||||
class ProjectAssignmentV3(controller.V3Controller):
|
||||
"""The V3 Project APIs that are processing assignments."""
|
||||
|
||||
|
|
|
@ -46,11 +46,184 @@ from keystone.common import controller
|
|||
from keystone.common import provider_api
|
||||
from keystone.common import utils
|
||||
from keystone.common import wsgi
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.token import controllers as token_controllers
|
||||
|
||||
CRED_TYPE_EC2 = 'ec2'
|
||||
CONF = keystone.conf.CONF
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
class V2TokenDataHelper(provider_api.ProviderAPIMixin, object):
|
||||
"""Create V2 token data."""
|
||||
|
||||
def v3_to_v2_token(self, v3_token_data, token_id):
|
||||
"""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
|
||||
:param token_id: ID of the token being converted
|
||||
: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 v3_token.get('bind'):
|
||||
token['bind'] = v3_token['bind']
|
||||
token['id'] = token_id
|
||||
|
||||
if 'project' in v3_token:
|
||||
# v3 token_data does not contain all tenant attributes
|
||||
tenant = PROVIDERS.resource_api.get_project(
|
||||
v3_token['project']['id'])
|
||||
# Drop domain specific fields since v2 calls are not domain-aware.
|
||||
token['tenant'] = controller.V2Controller.v3_to_v2_project(
|
||||
tenant)
|
||||
token_data['token'] = token
|
||||
|
||||
# Build v2 user
|
||||
v3_user = v3_token['user']
|
||||
|
||||
user = controller.V2Controller.v3_to_v2_user(v3_user)
|
||||
|
||||
if 'OS-TRUST:trust' in v3_token:
|
||||
v3_trust = v3_token['OS-TRUST:trust']
|
||||
# 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
|
||||
msg = _('Non-default domain is not supported')
|
||||
if CONF.trust.enabled:
|
||||
try:
|
||||
trust_ref = PROVIDERS.trust_api.get_trust(v3_trust['id'])
|
||||
except exception.TrustNotFound:
|
||||
raise exception.TokenNotFound(token_id=token_id)
|
||||
trustee_user_ref = PROVIDERS.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 = PROVIDERS.identity_api.get_user(
|
||||
trust_ref['trustor_user_id'])
|
||||
if (trustor_user_ref['domain_id'] !=
|
||||
CONF.identity.default_domain_id):
|
||||
raise exception.Unauthorized(msg)
|
||||
if trust_ref.get('project_id'):
|
||||
project_ref = PROVIDERS.resource_api.get_project(
|
||||
trust_ref['project_id'])
|
||||
if (project_ref['domain_id'] !=
|
||||
CONF.identity.default_domain_id):
|
||||
raise exception.Unauthorized(msg)
|
||||
|
||||
token_data['trust'] = {
|
||||
'impersonation': v3_trust['impersonation'],
|
||||
'id': v3_trust['id'],
|
||||
'trustee_user_id': v3_trust['trustee_user']['id'],
|
||||
'trustor_user_id': v3_trust['trustor_user']['id']
|
||||
}
|
||||
|
||||
if 'OS-OAUTH1' in v3_token:
|
||||
msg = ('Unable to validate Oauth tokens using the version v2.0 '
|
||||
'API.')
|
||||
raise exception.Unauthorized(msg)
|
||||
|
||||
if 'OS-FEDERATION' in v3_token['user']:
|
||||
msg = _('Unable to validate Federation 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 = PROVIDERS.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_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())
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
|
@ -280,7 +453,7 @@ class Ec2Controller(Ec2ControllerCommon, controller.V2Controller):
|
|||
token_id, token_data = self.token_provider_api.issue_token(
|
||||
user_ref['id'], method_names, project_id=project_ref['id'])
|
||||
|
||||
v2_helper = token_controllers.V2TokenDataHelper()
|
||||
v2_helper = V2TokenDataHelper()
|
||||
token_data = v2_helper.v3_to_v2_token(token_data, token_id)
|
||||
return token_data
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ import hmac
|
|||
import six
|
||||
from six.moves import http_client
|
||||
|
||||
from keystone.common import extension
|
||||
from keystone.common import json_home
|
||||
from keystone.common import utils
|
||||
from keystone.common import wsgi
|
||||
|
@ -37,24 +36,6 @@ from keystone import exception
|
|||
from keystone.i18n import _
|
||||
|
||||
|
||||
EXTENSION_DATA = {
|
||||
'name': 'OpenStack S3 API',
|
||||
'namespace': 'https://docs.openstack.org/identity/api/ext/'
|
||||
's3tokens/v1.0',
|
||||
'alias': 's3tokens',
|
||||
'updated': '2013-07-07T12:00:0-00:00',
|
||||
'description': 'OpenStack S3 API.',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'https://developer.openstack.org/'
|
||||
'api-ref-identity-v2-ext.html',
|
||||
}
|
||||
]}
|
||||
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
|
||||
|
||||
class S3Extension(wsgi.V3ExtensionRouter):
|
||||
def add_routes(self, mapper):
|
||||
controller = S3Controller()
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
import uuid
|
||||
|
||||
from keystone.common import cache
|
||||
from keystone.common import extension
|
||||
from keystone.common import manager
|
||||
from keystone.common import provider_api
|
||||
import keystone.conf
|
||||
|
@ -30,20 +29,6 @@ MEMOIZE = cache.get_memoization_decorator(group='federation')
|
|||
|
||||
CONF = keystone.conf.CONF
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
EXTENSION_DATA = {
|
||||
'name': 'OpenStack Federation APIs',
|
||||
'namespace': 'https://docs.openstack.org/identity/api/ext/'
|
||||
'OS-FEDERATION/v1.0',
|
||||
'alias': 'OS-FEDERATION',
|
||||
'updated': '2013-12-17T12:00:0-00:00',
|
||||
'description': 'OpenStack Identity Providers Mechanism.',
|
||||
'links': [{
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'https://developer.openstack.org/api-ref-identity-v3-ext.html',
|
||||
}]}
|
||||
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
|
||||
|
||||
class Manager(manager.Manager):
|
||||
|
|
|
@ -22,7 +22,6 @@ import oauthlib.common
|
|||
from oauthlib import oauth1
|
||||
from oslo_log import log
|
||||
|
||||
from keystone.common import extension
|
||||
from keystone.common import manager
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
|
@ -58,25 +57,6 @@ def token_generator(*args, **kwargs):
|
|||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
EXTENSION_DATA = {
|
||||
'name': 'OpenStack OAUTH1 API',
|
||||
'namespace': 'https://docs.openstack.org/identity/api/ext/'
|
||||
'OS-OAUTH1/v1.0',
|
||||
'alias': 'OS-OAUTH1',
|
||||
'updated': '2013-07-07T12:00:0-00:00',
|
||||
'description': 'OpenStack OAuth 1.0a Delegated Auth Mechanism.',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'https://developer.openstack.org/'
|
||||
'api-ref-identity-v3-ext.html',
|
||||
}
|
||||
]}
|
||||
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
|
||||
|
||||
def get_oauth_headers(headers):
|
||||
parameters = {}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
"""Main entry point into the Revoke service."""
|
||||
|
||||
from keystone.common import cache
|
||||
from keystone.common import extension
|
||||
from keystone.common import manager
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
|
@ -24,25 +23,6 @@ from keystone import notifications
|
|||
|
||||
CONF = keystone.conf.CONF
|
||||
|
||||
|
||||
EXTENSION_DATA = {
|
||||
'name': 'OpenStack Revoke API',
|
||||
'namespace': 'https://docs.openstack.org/identity/api/ext/'
|
||||
'OS-REVOKE/v1.0',
|
||||
'alias': 'OS-REVOKE',
|
||||
'updated': '2014-02-24T20:51:0-00:00',
|
||||
'description': 'OpenStack revoked token reporting mechanism.',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'https://developer.openstack.org/'
|
||||
'api-ref-identity-v3-ext.html',
|
||||
}
|
||||
]}
|
||||
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||
|
||||
# This builds a discrete cache region dedicated to revoke events. The API can
|
||||
# return a filtered list based upon last fetchtime. This is deprecated but
|
||||
# must be maintained.
|
||||
|
|
|
@ -12,6 +12,5 @@
|
|||
# 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
|
||||
|
|
|
@ -17,7 +17,6 @@ import functools
|
|||
import webob
|
||||
|
||||
from keystone.common import controller
|
||||
from keystone.common import extension
|
||||
from keystone.common import json_home
|
||||
from keystone.common import wsgi
|
||||
import keystone.conf
|
||||
|
@ -25,23 +24,6 @@ from keystone import exception
|
|||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
EXTENSION_DATA = {
|
||||
'name': 'OpenStack Simple Certificate API',
|
||||
'namespace': 'https://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': 'https://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,
|
||||
|
|
|
@ -1,450 +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 keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import utils
|
||||
from keystone.common import wsgi
|
||||
import keystone.conf
|
||||
from keystone import exception
|
||||
from keystone.i18n import _
|
||||
from keystone.models import token_model
|
||||
from keystone.token.providers import common
|
||||
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
def authentication_method_generator(request, auth):
|
||||
"""Given an request return a suitable authentication method.
|
||||
|
||||
This is simply a generator to handle matching an authentication request
|
||||
with the appropriate authentication method.
|
||||
|
||||
:param auth: Dictionary containing authentication information from the
|
||||
request.
|
||||
:returns: An authentication method class object.
|
||||
"""
|
||||
if auth is None:
|
||||
raise exception.ValidationError(attribute='auth',
|
||||
target='request body')
|
||||
|
||||
if request.environ.get('REMOTE_USER'):
|
||||
method = ExternalAuthenticationMethod()
|
||||
elif 'token' in auth:
|
||||
method = TokenAuthenticationMethod()
|
||||
elif 'passwordCredentials' in auth:
|
||||
method = LocalAuthenticationMethod()
|
||||
else:
|
||||
raise exception.ValidationError(attribute='auth',
|
||||
target='request body')
|
||||
return method
|
||||
|
||||
|
||||
class ExternalAuthNotApplicable(Exception):
|
||||
"""External authentication is not applicable."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BaseAuthenticationMethod(provider_api.ProviderAPIMixin, object):
|
||||
"""Common utilities/dependencies for all authentication method classes."""
|
||||
|
||||
def _get_project_id_from_auth(self, auth):
|
||||
"""Extract and normalize project information from auth dict.
|
||||
|
||||
:param auth: Dictionary representing the authentication request.
|
||||
:returns: A string representing the project in the authentication
|
||||
request. If project scope isn't present in the request None
|
||||
is returned.
|
||||
"""
|
||||
project_id = auth.get('tenantId')
|
||||
project_name = auth.get('tenantName')
|
||||
|
||||
if project_id:
|
||||
if len(project_id) > CONF.max_param_size:
|
||||
raise exception.ValidationSizeError(
|
||||
attribute='tenantId', size=CONF.max_param_size
|
||||
)
|
||||
elif project_name:
|
||||
if len(project_name) > CONF.max_param_size:
|
||||
raise exception.ValidationSizeError(
|
||||
attribute='tenantName', size=CONF.max_param_size
|
||||
)
|
||||
if (CONF.resource.project_name_url_safe == 'strict' and
|
||||
utils.is_not_url_safe(project_name)):
|
||||
msg = _('Tenant name cannot contain reserved characters.')
|
||||
raise exception.Unauthorized(message=msg)
|
||||
try:
|
||||
project_id = PROVIDERS.resource_api.get_project_by_name(
|
||||
project_name, CONF.identity.default_domain_id
|
||||
)['id']
|
||||
except exception.ProjectNotFound as e:
|
||||
raise exception.Unauthorized(e)
|
||||
else:
|
||||
project_id = None
|
||||
|
||||
return project_id
|
||||
|
||||
|
||||
class TokenAuthenticationMethod(BaseAuthenticationMethod):
|
||||
"""Authenticate using an existing token."""
|
||||
|
||||
def _restrict_scope(self, token_model_ref):
|
||||
"""Determine if rescoping is allowed based on the token model.
|
||||
|
||||
:param token_model_ref: `keystone.models.token.KeystoneToken` object.
|
||||
"""
|
||||
# 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(self, request, auth):
|
||||
"""Try to authenticate using an already existing token.
|
||||
|
||||
:param request: A request object.
|
||||
:param auth: Dictionary representing the authentication request.
|
||||
:returns: A tuple containing the user reference, project identifier,
|
||||
token expiration, bind information, and original audit
|
||||
information.
|
||||
"""
|
||||
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:
|
||||
v3_token_data = PROVIDERS.token_provider_api.validate_token(
|
||||
old_token
|
||||
)
|
||||
# NOTE(lbragstad): Even though we are not using the v2.0 token
|
||||
# reference after we translate it in v3_to_v2_token(), we still
|
||||
# need to perform that check. We have to do this because
|
||||
# v3_to_v2_token will ensure we don't use specific tokens only
|
||||
# attainable via v3 to get new tokens on v2.0. For example, an
|
||||
# exception would be raised if we passed a federated token to
|
||||
# v3_to_v2_token, because federated tokens aren't supported by
|
||||
# v2.0 (the same applies to OAuth tokens, domain-scoped tokens,
|
||||
# etc..).
|
||||
v2_helper = V2TokenDataHelper()
|
||||
v2_helper.v3_to_v2_token(v3_token_data, old_token)
|
||||
token_model_ref = token_model.KeystoneToken(
|
||||
token_id=old_token,
|
||||
token_data=v3_token_data
|
||||
)
|
||||
except exception.NotFound as e:
|
||||
raise exception.Unauthorized(e)
|
||||
|
||||
wsgi.validate_token_bind(request.context_dict, token_model_ref)
|
||||
|
||||
self._restrict_scope(token_model_ref)
|
||||
user_id = token_model_ref.user_id
|
||||
project_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 = PROVIDERS.trust_api.get_trust(auth['trust_id'])
|
||||
except exception.TrustNotFound:
|
||||
raise exception.Forbidden()
|
||||
# If a trust is being used to obtain access to another project and
|
||||
# the other project doesn't match the project in the trust, we need
|
||||
# to bail because trusts are only good up to a single project.
|
||||
if (trust_ref['project_id'] and
|
||||
project_id != trust_ref['project_id']):
|
||||
raise exception.Forbidden()
|
||||
|
||||
expiry = token_model_ref.expires
|
||||
user_ref = PROVIDERS.identity_api.get_user(user_id)
|
||||
bind = token_model_ref.bind
|
||||
original_audit_id = token_model_ref.audit_chain_id
|
||||
return (user_ref, project_id, expiry, bind, original_audit_id)
|
||||
|
||||
|
||||
class LocalAuthenticationMethod(BaseAuthenticationMethod):
|
||||
"""Authenticate against a local backend using password credentials."""
|
||||
|
||||
def authenticate(self, request, auth):
|
||||
"""Try to authenticate against the identity backend.
|
||||
|
||||
:param request: A request object.
|
||||
:param auth: Dictionary representing the authentication request.
|
||||
:returns: A tuple containing the user reference, project identifier,
|
||||
token expiration, bind information, and original audit
|
||||
information.
|
||||
"""
|
||||
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 = PROVIDERS.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 = PROVIDERS.identity_api.authenticate(
|
||||
request,
|
||||
user_id=user_id,
|
||||
password=password)
|
||||
except AssertionError as e:
|
||||
raise exception.Unauthorized(e.args[0])
|
||||
|
||||
project_id = self._get_project_id_from_auth(auth)
|
||||
expiry = common.default_expire_time()
|
||||
bind = None
|
||||
audit_id = None
|
||||
return (user_ref, project_id, expiry, bind, audit_id)
|
||||
|
||||
|
||||
class ExternalAuthenticationMethod(BaseAuthenticationMethod):
|
||||
"""Authenticate using an external authentication method."""
|
||||
|
||||
def authenticate(self, request, auth):
|
||||
"""Try to authenticate an external user via REMOTE_USER variable.
|
||||
|
||||
:param request: A request object.
|
||||
:param auth: Dictionary representing the authentication request.
|
||||
:returns: A tuple containing the user reference, project identifier,
|
||||
token expiration, bind information, and original audit
|
||||
information.
|
||||
"""
|
||||
username = request.environ.get('REMOTE_USER')
|
||||
|
||||
if not username:
|
||||
raise ExternalAuthNotApplicable()
|
||||
|
||||
try:
|
||||
user_ref = PROVIDERS.identity_api.get_user_by_name(
|
||||
username, CONF.identity.default_domain_id)
|
||||
except exception.UserNotFound as e:
|
||||
raise exception.Unauthorized(e)
|
||||
|
||||
tenant_id = self._get_project_id_from_auth(auth)
|
||||
expiry = common.default_expire_time()
|
||||
bind = None
|
||||
if ('kerberos' in CONF.token.bind and
|
||||
request.environ.get('AUTH_TYPE', '').lower() == 'negotiate'):
|
||||
bind = {'kerberos': username}
|
||||
audit_id = None
|
||||
return (user_ref, tenant_id, expiry, bind, audit_id)
|
||||
|
||||
|
||||
class V2TokenDataHelper(provider_api.ProviderAPIMixin, object):
|
||||
"""Create V2 token data."""
|
||||
|
||||
def v3_to_v2_token(self, v3_token_data, token_id):
|
||||
"""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
|
||||
:param token_id: ID of the token being converted
|
||||
: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 v3_token.get('bind'):
|
||||
token['bind'] = v3_token['bind']
|
||||
token['id'] = token_id
|
||||
|
||||
if 'project' in v3_token:
|
||||
# v3 token_data does not contain all tenant attributes
|
||||
tenant = PROVIDERS.resource_api.get_project(
|
||||
v3_token['project']['id'])
|
||||
# Drop domain specific fields since v2 calls are not domain-aware.
|
||||
token['tenant'] = controller.V2Controller.v3_to_v2_project(
|
||||
tenant)
|
||||
token_data['token'] = token
|
||||
|
||||
# Build v2 user
|
||||
v3_user = v3_token['user']
|
||||
|
||||
user = controller.V2Controller.v3_to_v2_user(v3_user)
|
||||
|
||||
if 'OS-TRUST:trust' in v3_token:
|
||||
v3_trust = v3_token['OS-TRUST:trust']
|
||||
# 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
|
||||
msg = _('Non-default domain is not supported')
|
||||
if CONF.trust.enabled:
|
||||
try:
|
||||
trust_ref = PROVIDERS.trust_api.get_trust(v3_trust['id'])
|
||||
except exception.TrustNotFound:
|
||||
raise exception.TokenNotFound(token_id=token_id)
|
||||
trustee_user_ref = PROVIDERS.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 = PROVIDERS.identity_api.get_user(
|
||||
trust_ref['trustor_user_id'])
|
||||
if (trustor_user_ref['domain_id'] !=
|
||||
CONF.identity.default_domain_id):
|
||||
raise exception.Unauthorized(msg)
|
||||
if trust_ref.get('project_id'):
|
||||
project_ref = PROVIDERS.resource_api.get_project(
|
||||
trust_ref['project_id'])
|
||||
if (project_ref['domain_id'] !=
|
||||
CONF.identity.default_domain_id):
|
||||
raise exception.Unauthorized(msg)
|
||||
|
||||
token_data['trust'] = {
|
||||
'impersonation': v3_trust['impersonation'],
|
||||
'id': v3_trust['id'],
|
||||
'trustee_user_id': v3_trust['trustee_user']['id'],
|
||||
'trustor_user_id': v3_trust['trustor_user']['id']
|
||||
}
|
||||
|
||||
if 'OS-OAUTH1' in v3_token:
|
||||
msg = ('Unable to validate Oauth tokens using the version v2.0 '
|
||||
'API.')
|
||||
raise exception.Unauthorized(msg)
|
||||
|
||||
if 'OS-FEDERATION' in v3_token['user']:
|
||||
msg = _('Unable to validate Federation 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 = PROVIDERS.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_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())
|
|
@ -1,55 +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 import assignment
|
||||
from keystone.common import extension
|
||||
from keystone.common import wsgi
|
||||
|
||||
|
||||
extension.register_admin_extension(
|
||||
'OS-KSADM', {
|
||||
'name': 'OpenStack Keystone Admin',
|
||||
'namespace': 'https://docs.openstack.org/identity/api/ext/'
|
||||
'OS-KSADM/v1.0',
|
||||
'alias': 'OS-KSADM',
|
||||
'updated': '2013-07-11T17:14:00-00:00',
|
||||
'description': 'OpenStack extensions to Keystone v2.0 API '
|
||||
'enabling Administrative Operations.',
|
||||
'links': [
|
||||
{
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'https://developer.openstack.org/'
|
||||
'api-ref-identity-v2-ext.html',
|
||||
}
|
||||
]})
|
||||
|
||||
|
||||
class Router(wsgi.ComposableRouter):
|
||||
"""Previously known as the OS-KSADM extension.
|
||||
|
||||
Provides a bunch of CRUD operations for internal data types.
|
||||
|
||||
"""
|
||||
|
||||
def add_routes(self, mapper):
|
||||
assignment_tenant_controller = (
|
||||
assignment.controllers.TenantAssignment())
|
||||
|
||||
# Tenant Operations
|
||||
mapper.connect(
|
||||
'/tenants/{tenant_id}/users',
|
||||
controller=assignment_tenant_controller,
|
||||
action='get_project_users',
|
||||
conditions=dict(method=['GET']))
|
|
@ -36,7 +36,6 @@ from keystone.resource import routers as resource_routers
|
|||
from keystone.revoke import routers as revoke_routers
|
||||
from keystone.token import _simple_cert as simple_cert_ext
|
||||
from keystone.trust import routers as trust_routers
|
||||
from keystone.v2_crud import admin_crud
|
||||
from keystone.version import controllers
|
||||
from keystone.version import routers
|
||||
|
||||
|
@ -82,20 +81,24 @@ def warn_local_conf(f):
|
|||
@warn_local_conf
|
||||
def public_app_factory(global_conf, **local_conf):
|
||||
controllers.register_version('v2.0')
|
||||
return wsgi.ComposingRouter(routes.Mapper(),
|
||||
[assignment_routers.Public(),
|
||||
routers.VersionV2('public'),
|
||||
routers.Extension(False)])
|
||||
# NOTE(lbragstad): Only wire up the v2.0 version controller. We should keep
|
||||
# this here because we still support the ec2tokens API on the v2.0 path
|
||||
# until T. Once that is removed, we can remove the rest of the v2.0 routers
|
||||
# and whatnot. The ec2token controller is actually wired up by the paste
|
||||
# pipeline.
|
||||
return wsgi.ComposingRouter(routes.Mapper(), [routers.VersionV2('public')])
|
||||
|
||||
|
||||
@fail_gracefully
|
||||
@warn_local_conf
|
||||
def admin_app_factory(global_conf, **local_conf):
|
||||
controllers.register_version('v2.0')
|
||||
return wsgi.ComposingRouter(routes.Mapper(),
|
||||
[admin_crud.Router(),
|
||||
routers.VersionV2('admin'),
|
||||
routers.Extension()])
|
||||
# NOTE(lbragstad): Only wire up the v2.0 version controller. We should keep
|
||||
# this here because we still support the ec2tokens API on the v2.0 path
|
||||
# until T. Once that is removed, we can remove the rest of the v2.0 routers
|
||||
# and whatnot. The ec2token controller is actually wired up by the paste
|
||||
# pipeline.
|
||||
return wsgi.ComposingRouter(routes.Mapper(), [routers.VersionV2('admin')])
|
||||
|
||||
|
||||
@fail_gracefully
|
||||
|
|
Loading…
Reference in New Issue