Remove all v2.0 APIs except the ec2tokens API
The only API we're supporting on the v2.0 path until the T release is the ec2tokens API. This commit removes all routers from the public and admin v2.0 applications. This includes the extensions API. This commit also removes unused v2.0 controller logic. Change-Id: I523c1215899ac9ee605df6bf717643c0ba87c761 Closes-Bug: 1746798
This commit is contained in:
parent
ad1968d9ec
commit
d5e9c0b4fe
|
@ -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
|
||||
|
@ -29,20 +28,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