Change the default version discovery URLs
The default discovery URLs for when the admin_endpoint and public_endpoint configuration values are unset is to point to the localhost. This is wrong in all but the most trivial cases. It also has the problem of not being able to distinguish for the public service whether it was accessed via the 'public' or 'private' endpoint, meaning that all clients that correctly do discovery will end up routing to the public URL. The most sensible default is to simply use the requested URL as the basis for pointing to the versioned endpoints as it at least assumes that the endpoint is accessible relative to the location used to arrive on the page. As mentioned in comments this is not a perfect solution. HOST_URL is the URL not including path (ie http://server:port) so we do not have access to the prefix automatically. Unfortunately the way keystone uses these endpoints I don't see a way of improving that without a more substantial redesign. This patch is ugly because our layers are so intertwined. It should be nicer with pecan. DocImpact: Changes the default values of admin_endpoint and public_endpoint and how they are used. In most situations now these values should be ignored in configuration. Change-Id: Ia6d9fbeb60ada661dc2052c9bd51db7a1dc8cd4b Closes-Bug: #1288009
This commit is contained in:
parent
f42b8083cd
commit
7a760caa5d
@ -36,12 +36,24 @@
|
||||
|
||||
# The base public endpoint URL for keystone that are
|
||||
# advertised to clients (NOTE: this does NOT affect how
|
||||
# keystone listens for connections). (string value)
|
||||
# keystone listens for connections) (string value).
|
||||
# Defaults to the base host URL of the request. Eg a
|
||||
# request to http://server:5000/v2.0/users will
|
||||
# default to http://server:5000. You should only need
|
||||
# to set this value if the base URL contains a path
|
||||
# (eg /prefix/v2.0) or the endpoint should be found on
|
||||
# a different server.
|
||||
#public_endpoint=http://localhost:%(public_port)s/
|
||||
|
||||
# The base admin endpoint URL for keystone that are advertised
|
||||
# to clients (NOTE: this does NOT affect how keystone listens
|
||||
# for connections). (string value)
|
||||
# for connections) (string value).
|
||||
# Defaults to the base host URL of the request. Eg a
|
||||
# request to http://server:35357/v2.0/users will
|
||||
# default to http://server:35357. You should only need
|
||||
# to set this value if the base URL contains a path
|
||||
# (eg /prefix/v2.0) or the endpoint should be found on
|
||||
# a different server.
|
||||
#admin_endpoint=http://localhost:%(admin_port)s/
|
||||
|
||||
# onready allows you to send a notification when the process
|
||||
|
@ -574,7 +574,7 @@ class RoleAssignmentV3(controller.V3Controller):
|
||||
# the wrapper as have already included the links in the entities
|
||||
pass
|
||||
|
||||
def _format_entity(self, entity):
|
||||
def _format_entity(self, context, entity):
|
||||
"""Format an assignment entity for API response.
|
||||
|
||||
The driver layer returns entities as dicts containing the ids of the
|
||||
@ -641,16 +641,17 @@ class RoleAssignmentV3(controller.V3Controller):
|
||||
else:
|
||||
target_link = '/domains/%s' % entity['domain_id']
|
||||
formatted_entity.setdefault('links', {})
|
||||
formatted_entity['links']['assignment'] = (
|
||||
self.base_url('%(target)s/%(actor)s/roles/%(role)s%(suffix)s' % {
|
||||
'target': target_link,
|
||||
'actor': actor_link,
|
||||
'role': entity['role_id'],
|
||||
'suffix': suffix}))
|
||||
|
||||
path = '%(target)s/%(actor)s/roles/%(role)s%(suffix)s' % {
|
||||
'target': target_link,
|
||||
'actor': actor_link,
|
||||
'role': entity['role_id'],
|
||||
'suffix': suffix}
|
||||
formatted_entity['links']['assignment'] = self.base_url(context, path)
|
||||
|
||||
return formatted_entity
|
||||
|
||||
def _expand_indirect_assignments(self, refs):
|
||||
def _expand_indirect_assignments(self, context, refs):
|
||||
"""Processes entity list into all-direct assignments.
|
||||
|
||||
For any group role assignments in the list, create a role assignment
|
||||
@ -710,7 +711,7 @@ class RoleAssignmentV3(controller.V3Controller):
|
||||
user_entry = copy.deepcopy(template)
|
||||
user_entry['user'] = {'id': user['id']}
|
||||
user_entry['links']['membership'] = (
|
||||
self.base_url('/groups/%s/users/%s' %
|
||||
self.base_url(context, '/groups/%s/users/%s' %
|
||||
(group_id, user['id'])))
|
||||
return user_entry
|
||||
|
||||
@ -727,6 +728,7 @@ class RoleAssignmentV3(controller.V3Controller):
|
||||
project_entry['scope']['project'] = {'id': project_id}
|
||||
project_entry['links']['assignment'] = (
|
||||
self.base_url(
|
||||
context,
|
||||
'/OS-INHERIT/domains/%s/users/%s/roles/%s'
|
||||
'/inherited_to_projects' % (
|
||||
domain_id, project_entry['user']['id'],
|
||||
@ -746,12 +748,13 @@ class RoleAssignmentV3(controller.V3Controller):
|
||||
project_entry['user'] = {'id': user_id}
|
||||
project_entry['scope']['project'] = {'id': project_id}
|
||||
project_entry['links']['assignment'] = (
|
||||
self.base_url('/OS-INHERIT/domains/%s/groups/%s/roles/%s'
|
||||
self.base_url(context,
|
||||
'/OS-INHERIT/domains/%s/groups/%s/roles/%s'
|
||||
'/inherited_to_projects' % (
|
||||
domain_id, group_id,
|
||||
project_entry['role']['id'])))
|
||||
project_entry['links']['membership'] = (
|
||||
self.base_url('/groups/%s/users/%s' %
|
||||
self.base_url(context, '/groups/%s/users/%s' %
|
||||
(group_id, user_id)))
|
||||
return project_entry
|
||||
|
||||
@ -862,14 +865,15 @@ class RoleAssignmentV3(controller.V3Controller):
|
||||
hints = self.build_driver_hints(context, filters)
|
||||
refs = self.assignment_api.list_role_assignments()
|
||||
formatted_refs = (
|
||||
[self._format_entity(x) for x in refs
|
||||
[self._format_entity(context, x) for x in refs
|
||||
if self._filter_inherited(x)])
|
||||
|
||||
if ('effective' in context['query_string'] and
|
||||
self._query_filter_is_true(
|
||||
context['query_string']['effective'])):
|
||||
|
||||
formatted_refs = self._expand_indirect_assignments(formatted_refs)
|
||||
formatted_refs = self._expand_indirect_assignments(context,
|
||||
formatted_refs)
|
||||
|
||||
return self.wrap_collection(context, formatted_refs, hints=hints)
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
from keystone import auth
|
||||
from keystone.common import controller
|
||||
from keystone.common import dependency
|
||||
from keystone.contrib.oauth1 import core as oauth
|
||||
from keystone.contrib.oauth1 import validator
|
||||
@ -54,7 +55,7 @@ class OAuth(auth.AuthMethodHandler):
|
||||
if now > expires:
|
||||
raise exception.Unauthorized(_('Access token is expired'))
|
||||
|
||||
url = oauth.rebuild_url(context['path'])
|
||||
url = controller.V3Controller.base_url(context, context['path'])
|
||||
access_verifier = oauth.ResourceEndpoint(
|
||||
request_validator=validator.OAuthValidator(),
|
||||
token_generator=oauth.token_generator)
|
||||
|
@ -50,15 +50,25 @@ FILE_OPTIONS = {
|
||||
help='The port number which the public service listens '
|
||||
'on.'),
|
||||
cfg.StrOpt('public_endpoint',
|
||||
default='http://localhost:%(public_port)s/',
|
||||
help='The base public endpoint URL for keystone that are '
|
||||
'advertised to clients (NOTE: this does NOT affect '
|
||||
'how keystone listens for connections).'),
|
||||
'how keystone listens for connections). '
|
||||
'Defaults to the base host URL of the request. Eg a '
|
||||
'request to http://server:5000/v2.0/users will '
|
||||
'default to http://server:5000. You should only need '
|
||||
'to set this value if the base URL contains a path '
|
||||
'(eg /prefix/v2.0) or the endpoint should be found on '
|
||||
'a different server.'),
|
||||
cfg.StrOpt('admin_endpoint',
|
||||
default='http://localhost:%(admin_port)s/',
|
||||
help='The base admin endpoint URL for keystone that are '
|
||||
'advertised to clients (NOTE: this does NOT affect '
|
||||
'how keystone listens for connections).'),
|
||||
'how keystone listens for connections). '
|
||||
'Defaults to the base host URL of the request. Eg a '
|
||||
'request to http://server:35357/v2.0/users will '
|
||||
'default to http://server:35357. You should only need '
|
||||
'to set this value if the base URL contains a path '
|
||||
'(eg /prefix/v2.0) or the endpoint should be found on '
|
||||
'a different server.'),
|
||||
cfg.StrOpt('onready',
|
||||
help='onready allows you to send a notification when the '
|
||||
'process is ready to serve For example, to have it '
|
||||
|
@ -292,28 +292,21 @@ class V3Controller(wsgi.Application):
|
||||
get_member_from_driver = None
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, path=None):
|
||||
endpoint = CONF.public_endpoint % CONF
|
||||
def base_url(cls, context, path=None):
|
||||
endpoint = super(V3Controller, cls).base_url(context, 'public')
|
||||
if not path:
|
||||
path = cls.collection_name
|
||||
|
||||
# allow a missing trailing slash in the config
|
||||
if endpoint[-1] != '/':
|
||||
endpoint += '/'
|
||||
|
||||
url = endpoint + 'v3'
|
||||
|
||||
if path:
|
||||
return url + path
|
||||
else:
|
||||
return url + '/' + cls.collection_name
|
||||
return '%s/%s/%s' % (endpoint, 'v3', path.lstrip('/'))
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, ref):
|
||||
def _add_self_referential_link(cls, context, ref):
|
||||
ref.setdefault('links', {})
|
||||
ref['links']['self'] = cls.base_url() + '/' + ref['id']
|
||||
ref['links']['self'] = cls.base_url(context) + '/' + ref['id']
|
||||
|
||||
@classmethod
|
||||
def wrap_member(cls, context, ref):
|
||||
cls._add_self_referential_link(ref)
|
||||
cls._add_self_referential_link(context, ref)
|
||||
return {cls.member_name: ref}
|
||||
|
||||
@classmethod
|
||||
@ -349,7 +342,7 @@ class V3Controller(wsgi.Application):
|
||||
container = {cls.collection_name: refs}
|
||||
container['links'] = {
|
||||
'next': None,
|
||||
'self': cls.base_url(path=context['path']),
|
||||
'self': cls.base_url(context, path=context['path']),
|
||||
'previous': None}
|
||||
|
||||
if list_limited:
|
||||
|
@ -185,6 +185,7 @@ class Application(BaseApplication):
|
||||
context['query_string'] = dict(six.iteritems(req.params))
|
||||
context['headers'] = dict(six.iteritems(req.headers))
|
||||
context['path'] = req.environ['PATH_INFO']
|
||||
context['host_url'] = req.host_url
|
||||
params = req.environ.get(PARAMS_ENV, {})
|
||||
#authentication and authorization attributes are set as environment
|
||||
#values by the container and processed by the pipeline. the complete
|
||||
@ -208,17 +209,21 @@ class Application(BaseApplication):
|
||||
LOG.warning(
|
||||
_('Authorization failed. %(exception)s from %(remote_addr)s'),
|
||||
{'exception': e, 'remote_addr': req.environ['REMOTE_ADDR']})
|
||||
return render_exception(e, user_locale=best_match_language(req))
|
||||
return render_exception(e, context=context,
|
||||
user_locale=best_match_language(req))
|
||||
except exception.Error as e:
|
||||
LOG.warning(e)
|
||||
return render_exception(e, user_locale=best_match_language(req))
|
||||
return render_exception(e, context=context,
|
||||
user_locale=best_match_language(req))
|
||||
except TypeError as e:
|
||||
LOG.exception(e)
|
||||
return render_exception(exception.ValidationError(e),
|
||||
context=context,
|
||||
user_locale=best_match_language(req))
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return render_exception(exception.UnexpectedError(exception=e),
|
||||
context=context,
|
||||
user_locale=best_match_language(req))
|
||||
|
||||
if result is None:
|
||||
@ -302,6 +307,22 @@ class Application(BaseApplication):
|
||||
|
||||
return token_ref.get('trust_id')
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, context, endpoint_type):
|
||||
url = CONF['%s_endpoint' % endpoint_type]
|
||||
|
||||
if url:
|
||||
url = url % CONF
|
||||
else:
|
||||
# NOTE(jamielennox): if url is not set via the config file we
|
||||
# should set it relative to the url that the user used to get here
|
||||
# so as not to mess with version discovery. This is not perfect.
|
||||
# host_url omits the path prefix, but there isn't another good
|
||||
# solution that will work for all urls.
|
||||
url = context['host_url']
|
||||
|
||||
return url.rstrip('/')
|
||||
|
||||
|
||||
class Middleware(Application):
|
||||
"""Base WSGI middleware.
|
||||
@ -370,15 +391,17 @@ class Middleware(Application):
|
||||
return self.process_response(request, response)
|
||||
except exception.Error as e:
|
||||
LOG.warning(e)
|
||||
return render_exception(e,
|
||||
return render_exception(e, request=request,
|
||||
user_locale=best_match_language(request))
|
||||
except TypeError as e:
|
||||
LOG.exception(e)
|
||||
return render_exception(exception.ValidationError(e),
|
||||
request=request,
|
||||
user_locale=best_match_language(request))
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
return render_exception(exception.UnexpectedError(exception=e),
|
||||
request=request,
|
||||
user_locale=best_match_language(request))
|
||||
|
||||
|
||||
@ -475,9 +498,10 @@ class Router(object):
|
||||
"""
|
||||
match = req.environ['wsgiorg.routing_args'][1]
|
||||
if not match:
|
||||
return render_exception(
|
||||
exception.NotFound(_('The resource could not be found.')),
|
||||
user_locale=best_match_language(req))
|
||||
msg = _('The resource could not be found.')
|
||||
return render_exception(exception.NotFound(msg),
|
||||
request=req,
|
||||
user_locale=best_match_language(req))
|
||||
app = match['controller']
|
||||
return app
|
||||
|
||||
@ -571,7 +595,7 @@ def render_response(body=None, status=None, headers=None):
|
||||
headerlist=headers)
|
||||
|
||||
|
||||
def render_exception(error, user_locale=None):
|
||||
def render_exception(error, context=None, request=None, user_locale=None):
|
||||
"""Forms a WSGI response based on the current error."""
|
||||
|
||||
error_message = error.args[0]
|
||||
@ -590,9 +614,18 @@ def render_exception(error, user_locale=None):
|
||||
if isinstance(error, exception.AuthPluginException):
|
||||
body['error']['identity'] = error.authentication
|
||||
elif isinstance(error, exception.Unauthorized):
|
||||
headers.append(('WWW-Authenticate',
|
||||
'Keystone uri="%s"' % (
|
||||
CONF.public_endpoint % CONF)))
|
||||
url = CONF.public_endpoint
|
||||
if not url:
|
||||
if request:
|
||||
context = {'host_url': request.host_url}
|
||||
if context:
|
||||
url = Application.base_url(context, 'public')
|
||||
else:
|
||||
url = 'http://localhost:%d' % CONF.public_port
|
||||
else:
|
||||
url = url % CONF
|
||||
|
||||
headers.append(('WWW-Authenticate', 'Keystone uri="%s"' % url))
|
||||
return render_response(status=(error.code, error.title),
|
||||
body=body,
|
||||
headers=headers)
|
||||
|
@ -28,11 +28,11 @@ class _ControllerBase(controller.V3Controller):
|
||||
"""Base behaviors for federation controllers."""
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, path=None):
|
||||
def base_url(cls, context, path=None):
|
||||
"""Construct a path and pass it to V3Controller.base_url method."""
|
||||
|
||||
path = '/OS-FEDERATION/' + cls.collection_name
|
||||
return controller.V3Controller.base_url(path=path)
|
||||
return super(_ControllerBase, cls).base_url(context, path=path)
|
||||
|
||||
|
||||
@dependency.requires('federation_api')
|
||||
@ -45,7 +45,7 @@ class IdentityProvider(_ControllerBase):
|
||||
_public_parameters = frozenset(['id', 'enabled', 'description', 'links'])
|
||||
|
||||
@classmethod
|
||||
def _add_related_links(cls, ref):
|
||||
def _add_related_links(cls, context, ref):
|
||||
"""Add URLs for entities related with Identity Provider.
|
||||
|
||||
Add URLs pointing to:
|
||||
@ -55,21 +55,22 @@ class IdentityProvider(_ControllerBase):
|
||||
ref.setdefault('links', {})
|
||||
base_path = ref['links'].get('self')
|
||||
if base_path is None:
|
||||
base_path = '/'.join([IdentityProvider.base_url(), ref['id']])
|
||||
base_path = '/'.join([IdentityProvider.base_url(context),
|
||||
ref['id']])
|
||||
for name in ['protocols']:
|
||||
ref['links'][name] = '/'.join([base_path, name])
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, ref):
|
||||
def _add_self_referential_link(cls, context, ref):
|
||||
id = ref.get('id')
|
||||
self_path = '/'.join([cls.base_url(), id])
|
||||
self_path = '/'.join([cls.base_url(context), id])
|
||||
ref.setdefault('links', {})
|
||||
ref['links']['self'] = self_path
|
||||
|
||||
@classmethod
|
||||
def wrap_member(cls, context, ref):
|
||||
cls._add_self_referential_link(ref)
|
||||
cls._add_related_links(ref)
|
||||
cls._add_self_referential_link(context, ref)
|
||||
cls._add_related_links(context, ref)
|
||||
ref = cls.filter_params(ref)
|
||||
return {cls.member_name: ref}
|
||||
|
||||
@ -134,7 +135,7 @@ class FederationProtocol(_ControllerBase):
|
||||
_mutable_parameters = frozenset(['mapping_id'])
|
||||
|
||||
@classmethod
|
||||
def _add_self_referential_link(cls, ref):
|
||||
def _add_self_referential_link(cls, context, ref):
|
||||
"""Add 'links' entry to the response dictionary.
|
||||
|
||||
Calls IdentityProvider.base_url() class method, as it constructs
|
||||
@ -146,14 +147,14 @@ class FederationProtocol(_ControllerBase):
|
||||
ref.setdefault('links', {})
|
||||
base_path = ref['links'].get('identity_provider')
|
||||
if base_path is None:
|
||||
base_path = [IdentityProvider.base_url(), ref['idp_id']]
|
||||
base_path = [IdentityProvider.base_url(context), ref['idp_id']]
|
||||
base_path = '/'.join(base_path)
|
||||
self_path = [base_path, 'protocols', ref['id']]
|
||||
self_path = '/'.join(self_path)
|
||||
ref['links']['self'] = self_path
|
||||
|
||||
@classmethod
|
||||
def _add_related_links(cls, ref):
|
||||
def _add_related_links(cls, context, ref):
|
||||
"""Add new entries to the 'links' subdictionary in the response.
|
||||
|
||||
Adds 'identity_provider' key with URL pointing to related identity
|
||||
@ -163,13 +164,14 @@ class FederationProtocol(_ControllerBase):
|
||||
|
||||
"""
|
||||
ref.setdefault('links', {})
|
||||
base_path = '/'.join([IdentityProvider.base_url(), ref['idp_id']])
|
||||
base_path = '/'.join([IdentityProvider.base_url(context),
|
||||
ref['idp_id']])
|
||||
ref['links']['identity_provider'] = base_path
|
||||
|
||||
@classmethod
|
||||
def wrap_member(cls, context, ref):
|
||||
cls._add_related_links(ref)
|
||||
cls._add_self_referential_link(ref)
|
||||
cls._add_related_links(context, ref)
|
||||
cls._add_self_referential_link(context, ref)
|
||||
ref = cls.filter_params(ref)
|
||||
return {cls.member_name: ref}
|
||||
|
||||
|
@ -35,13 +35,13 @@ class ConsumerCrudV3(controller.V3Controller):
|
||||
member_name = 'consumer'
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, path=None):
|
||||
def base_url(cls, context, path=None):
|
||||
"""Construct a path and pass it to V3Controller.base_url method."""
|
||||
|
||||
# NOTE(stevemar): Overriding path to /OS-OAUTH1/consumers so that
|
||||
# V3Controller.base_url handles setting the self link correctly.
|
||||
path = '/OS-OAUTH1/' + cls.collection_name
|
||||
return controller.V3Controller.base_url(path=path)
|
||||
return controller.V3Controller.base_url(context, path=path)
|
||||
|
||||
@controller.protected()
|
||||
def create_consumer(self, context, consumer):
|
||||
@ -90,13 +90,14 @@ class AccessTokenCrudV3(controller.V3Controller):
|
||||
access_token = self.oauth_api.get_access_token(access_token_id)
|
||||
if access_token['authorizing_user_id'] != user_id:
|
||||
raise exception.NotFound()
|
||||
access_token = self._format_token_entity(access_token)
|
||||
access_token = self._format_token_entity(context, access_token)
|
||||
return AccessTokenCrudV3.wrap_member(context, access_token)
|
||||
|
||||
@controller.protected()
|
||||
def list_access_tokens(self, context, user_id):
|
||||
refs = self.oauth_api.list_access_tokens(user_id)
|
||||
formatted_refs = ([self._format_token_entity(x) for x in refs])
|
||||
formatted_refs = ([self._format_token_entity(context, x)
|
||||
for x in refs])
|
||||
return AccessTokenCrudV3.wrap_collection(context, formatted_refs)
|
||||
|
||||
@controller.protected()
|
||||
@ -107,7 +108,7 @@ class AccessTokenCrudV3(controller.V3Controller):
|
||||
return self.oauth_api.delete_access_token(
|
||||
user_id, access_token_id)
|
||||
|
||||
def _format_token_entity(self, entity):
|
||||
def _format_token_entity(self, context, entity):
|
||||
|
||||
formatted_entity = entity.copy()
|
||||
access_token_id = formatted_entity['id']
|
||||
@ -124,7 +125,7 @@ class AccessTokenCrudV3(controller.V3Controller):
|
||||
'access_token_id': access_token_id})
|
||||
|
||||
formatted_entity.setdefault('links', {})
|
||||
formatted_entity['links']['roles'] = (self.base_url(url))
|
||||
formatted_entity['links']['roles'] = (self.base_url(context, url))
|
||||
|
||||
return formatted_entity
|
||||
|
||||
@ -185,7 +186,7 @@ class OAuthControllerV3(controller.V3Controller):
|
||||
raise exception.ValidationError(
|
||||
attribute='requested_project_id', target='request')
|
||||
|
||||
url = oauth1.rebuild_url(context['path'])
|
||||
url = self.base_url(context, context['path'])
|
||||
|
||||
req_headers = {'Requested-Project-Id': requested_project_id}
|
||||
req_headers.update(headers)
|
||||
@ -250,7 +251,7 @@ class OAuthControllerV3(controller.V3Controller):
|
||||
if now > expires:
|
||||
raise exception.Unauthorized(_('Request token is expired'))
|
||||
|
||||
url = oauth1.rebuild_url(context['path'])
|
||||
url = self.base_url(context, context['path'])
|
||||
|
||||
access_verifier = oauth1.AccessTokenEndpoint(
|
||||
request_validator=validator.OAuthValidator(),
|
||||
|
@ -106,17 +106,6 @@ def filter_token(access_token_ref):
|
||||
return access_token_ref
|
||||
|
||||
|
||||
def rebuild_url(path):
|
||||
endpoint = CONF.public_endpoint % CONF
|
||||
|
||||
# allow a missing trailing slash in the config
|
||||
if endpoint[-1] != '/':
|
||||
endpoint += '/'
|
||||
|
||||
url = endpoint + 'v3'
|
||||
return url + path
|
||||
|
||||
|
||||
def get_oauth_headers(headers):
|
||||
parameters = {}
|
||||
|
||||
|
@ -36,6 +36,7 @@ class RevokeController(controller.V3Controller):
|
||||
'links': {
|
||||
'next': None,
|
||||
'self': RevokeController.base_url(
|
||||
context,
|
||||
path=context['path']) + '/events',
|
||||
'previous': None}
|
||||
}
|
||||
|
@ -69,12 +69,10 @@ class Version(wsgi.Application):
|
||||
|
||||
super(Version, self).__init__()
|
||||
|
||||
def _get_identity_url(self, version='v2.0'):
|
||||
def _get_identity_url(self, context, version):
|
||||
"""Returns a URL to keystone's own endpoint."""
|
||||
url = CONF['%s_endpoint' % self.endpoint_url_type] % CONF
|
||||
if url[-1] != '/':
|
||||
url += '/'
|
||||
return '%s%s/' % (url, version)
|
||||
url = self.base_url(context, self.endpoint_url_type)
|
||||
return '%s/%s/' % (url, version)
|
||||
|
||||
def _get_versions_list(self, context):
|
||||
"""The list of versions is dependent on the context."""
|
||||
@ -87,7 +85,7 @@ class Version(wsgi.Application):
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': self._get_identity_url(version='v2.0'),
|
||||
'href': self._get_identity_url(context, 'v2.0'),
|
||||
}, {
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
@ -120,7 +118,7 @@ class Version(wsgi.Application):
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': self._get_identity_url(version='v3'),
|
||||
'href': self._get_identity_url(context, 'v3'),
|
||||
}
|
||||
],
|
||||
'media-types': [
|
||||
|
@ -118,7 +118,7 @@ class JsonBodyMiddleware(wsgi.Middleware):
|
||||
if request.content_type not in ('application/json', ''):
|
||||
e = exception.ValidationError(attribute='application/json',
|
||||
target='Content-Type header')
|
||||
return wsgi.render_exception(e)
|
||||
return wsgi.render_exception(e, request=request)
|
||||
|
||||
params_parsed = {}
|
||||
try:
|
||||
@ -126,7 +126,7 @@ class JsonBodyMiddleware(wsgi.Middleware):
|
||||
except ValueError:
|
||||
e = exception.ValidationError(attribute='valid JSON',
|
||||
target='request body')
|
||||
return wsgi.render_exception(e)
|
||||
return wsgi.render_exception(e, request=request)
|
||||
finally:
|
||||
if not params_parsed:
|
||||
params_parsed = {}
|
||||
@ -166,7 +166,7 @@ class XmlBodyMiddleware(wsgi.Middleware):
|
||||
LOG.exception('Serializer failed')
|
||||
e = exception.ValidationError(attribute='valid XML',
|
||||
target='request body')
|
||||
return wsgi.render_exception(e)
|
||||
return wsgi.render_exception(e, request=request)
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""Transform the response from JSON to XML."""
|
||||
|
@ -35,6 +35,8 @@ CONF = config.CONF
|
||||
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
|
||||
DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
|
||||
|
||||
HOST_URL = 'http://keystone:5001'
|
||||
|
||||
|
||||
def _build_user_auth(token=None, user_id=None, username=None,
|
||||
password=None, tenant_id=None, tenant_name=None,
|
||||
@ -674,7 +676,8 @@ class AuthWithTrust(AuthTest):
|
||||
auth_context = authorization.token_to_auth_context(
|
||||
token_ref['token_data'])
|
||||
return {'environment': {authorization.AUTH_CONTEXT_ENV: auth_context},
|
||||
'token_id': token_id}
|
||||
'token_id': token_id,
|
||||
'host_url': HOST_URL}
|
||||
|
||||
def create_trust(self, expires_at=None, impersonation=True):
|
||||
username = self.trustor['name']
|
||||
@ -723,9 +726,9 @@ class AuthWithTrust(AuthTest):
|
||||
role_ids = [self.role_browser['id'], self.role_member['id']]
|
||||
self.assertTrue(timeutils.parse_strtime(self.new_trust['expires_at'],
|
||||
fmt=TIME_FORMAT))
|
||||
self.assertIn('http://localhost:5000/v3/OS-TRUST/',
|
||||
self.assertIn('%s/v3/OS-TRUST/' % HOST_URL,
|
||||
self.new_trust['links']['self'])
|
||||
self.assertIn('http://localhost:5000/v3/OS-TRUST/',
|
||||
self.assertIn('%s/v3/OS-TRUST/' % HOST_URL,
|
||||
self.new_trust['roles_links']['self'])
|
||||
|
||||
for role in self.new_trust['roles']:
|
||||
@ -743,7 +746,8 @@ class AuthWithTrust(AuthTest):
|
||||
expires_at="Z")
|
||||
|
||||
def test_get_trust(self):
|
||||
context = {'token_id': self.unscoped_token['access']['token']['id']}
|
||||
context = {'token_id': self.unscoped_token['access']['token']['id'],
|
||||
'host_url': HOST_URL}
|
||||
trust = self.trust_controller.get_trust(context,
|
||||
self.new_trust['id'])['trust']
|
||||
self.assertEqual(self.trustor['id'], trust['trustor_user_id'])
|
||||
|
@ -681,7 +681,16 @@ class CoreApiTests(object):
|
||||
r = self.public_request(
|
||||
path='/v2.0/tenants',
|
||||
expected_status=401)
|
||||
self.assertEqual('Keystone uri="%s"' % (CONF.public_endpoint % CONF),
|
||||
self.assertEqual('Keystone uri="http://localhost"',
|
||||
r.headers.get('WWW-Authenticate'))
|
||||
|
||||
def test_www_authenticate_header_host(self):
|
||||
test_url = 'http://%s:4187' % uuid.uuid4().hex
|
||||
self.config_fixture.config(public_endpoint=test_url)
|
||||
r = self.public_request(
|
||||
path='/v2.0/tenants',
|
||||
expected_status=401)
|
||||
self.assertEqual('Keystone uri="%s"' % test_url,
|
||||
r.headers.get('WWW-Authenticate'))
|
||||
|
||||
|
||||
|
@ -17,6 +17,7 @@ import uuid
|
||||
|
||||
from lxml import etree
|
||||
import six
|
||||
from testtools import matchers
|
||||
|
||||
from keystone import auth
|
||||
from keystone.common import authorization
|
||||
@ -395,19 +396,17 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase):
|
||||
def assertValidListLinks(self, links):
|
||||
self.assertIsNotNone(links)
|
||||
self.assertIsNotNone(links.get('self'))
|
||||
self.assertIn(CONF.public_endpoint % CONF, links['self'])
|
||||
self.assertThat(links['self'], matchers.StartsWith('http://localhost'))
|
||||
|
||||
self.assertIn('next', links)
|
||||
if links['next'] is not None:
|
||||
self.assertIn(
|
||||
CONF.public_endpoint % CONF,
|
||||
links['next'])
|
||||
self.assertThat(links['next'],
|
||||
matchers.StartsWith('http://localhost'))
|
||||
|
||||
self.assertIn('previous', links)
|
||||
if links['previous'] is not None:
|
||||
self.assertIn(
|
||||
CONF.public_endpoint % CONF,
|
||||
links['previous'])
|
||||
self.assertThat(links['previous'],
|
||||
matchers.StartsWith('http://localhost'))
|
||||
|
||||
def assertValidListResponse(self, resp, key, entity_validator, ref=None,
|
||||
expected_length=None, keys_to_check=None):
|
||||
@ -467,7 +466,8 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase):
|
||||
|
||||
self.assertIsNotNone(entity.get('links'))
|
||||
self.assertIsNotNone(entity['links'].get('self'))
|
||||
self.assertIn(CONF.public_endpoint % CONF, entity['links']['self'])
|
||||
self.assertThat(entity['links']['self'],
|
||||
matchers.StartsWith('http://localhost'))
|
||||
self.assertIn(entity['id'], entity['links']['self'])
|
||||
|
||||
if ref:
|
||||
|
@ -51,7 +51,7 @@ class OAuth1Tests(test_v3.RestfulTestCase):
|
||||
super(OAuth1Tests, self).setUp()
|
||||
|
||||
# Now that the app has been served, we can query CONF values
|
||||
self.base_url = (CONF.public_endpoint % CONF) + "v3"
|
||||
self.base_url = 'http://localhost/v3'
|
||||
self.controller = controllers.OAuthControllerV3()
|
||||
|
||||
def _create_single_consumer(self):
|
||||
@ -153,7 +153,7 @@ class ConsumerCRUDTests(OAuth1Tests):
|
||||
consumer = self._create_single_consumer()
|
||||
consumer_id = consumer['id']
|
||||
resp = self.get(self.CONSUMER_URL + '/%s' % consumer_id)
|
||||
self_url = [CONF.public_endpoint % CONF, 'v3', self.CONSUMER_URL,
|
||||
self_url = ['http://localhost/v3', self.CONSUMER_URL,
|
||||
'/', consumer_id]
|
||||
self_url = ''.join(self_url)
|
||||
self.assertEqual(resp.result['consumer']['links']['self'], self_url)
|
||||
@ -164,7 +164,7 @@ class ConsumerCRUDTests(OAuth1Tests):
|
||||
resp = self.get(self.CONSUMER_URL)
|
||||
entities = resp.result['consumers']
|
||||
self.assertIsNotNone(entities)
|
||||
self_url = [CONF.public_endpoint % CONF, 'v3', self.CONSUMER_URL]
|
||||
self_url = ['http://localhost/v3', self.CONSUMER_URL]
|
||||
self_url = ''.join(self_url)
|
||||
self.assertEqual(resp.result['links']['self'], self_url)
|
||||
self.assertValidListLinks(resp.result['links'])
|
||||
|
@ -118,6 +118,10 @@ class VersionTestCase(tests.TestCase):
|
||||
self.public_app = self.loadapp('keystone', 'main')
|
||||
self.admin_app = self.loadapp('keystone', 'admin')
|
||||
|
||||
self.config_fixture.config(
|
||||
public_endpoint='http://localhost:%(public_port)d',
|
||||
admin_endpoint='http://localhost:%(admin_port)d')
|
||||
|
||||
fixture = self.useFixture(moxstubout.MoxStubout())
|
||||
self.stubs = fixture.stubs
|
||||
|
||||
@ -161,6 +165,25 @@ class VersionTestCase(tests.TestCase):
|
||||
version, 'http://localhost:%s/v2.0/' % CONF.admin_port)
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
def test_use_site_url_if_endpoint_unset(self):
|
||||
self.config_fixture.config(public_endpoint=None, admin_endpoint=None)
|
||||
|
||||
for app in (self.public_app, self.admin_app):
|
||||
client = self.client(app)
|
||||
resp = client.get('/')
|
||||
self.assertEqual(resp.status_int, 300)
|
||||
data = jsonutils.loads(resp.body)
|
||||
expected = VERSIONS_RESPONSE
|
||||
for version in expected['versions']['values']:
|
||||
# localhost happens to be the site url for tests
|
||||
if version['id'] == 'v3.0':
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost/v3/')
|
||||
elif version['id'] == 'v2.0':
|
||||
self._paste_in_port(
|
||||
version, 'http://localhost/v2.0/')
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
def test_public_version_v2(self):
|
||||
client = self.client(self.public_app)
|
||||
resp = client.get('/v2.0/')
|
||||
@ -181,6 +204,17 @@ class VersionTestCase(tests.TestCase):
|
||||
'http://localhost:%s/v2.0/' % CONF.admin_port)
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
def test_use_site_url_if_endpoint_unset_v2(self):
|
||||
self.config_fixture.config(public_endpoint=None, admin_endpoint=None)
|
||||
for app in (self.public_app, self.admin_app):
|
||||
client = self.client(app)
|
||||
resp = client.get('/v2.0/')
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
data = jsonutils.loads(resp.body)
|
||||
expected = v2_VERSION_RESPONSE
|
||||
self._paste_in_port(expected['version'], 'http://localhost/v2.0/')
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
def test_public_version_v3(self):
|
||||
client = self.client(self.public_app)
|
||||
resp = client.get('/v3/')
|
||||
@ -201,6 +235,17 @@ class VersionTestCase(tests.TestCase):
|
||||
'http://localhost:%s/v3/' % CONF.admin_port)
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
def test_use_site_url_if_endpoint_unset_v3(self):
|
||||
self.config_fixture.config(public_endpoint=None, admin_endpoint=None)
|
||||
for app in (self.public_app, self.admin_app):
|
||||
client = self.client(app)
|
||||
resp = client.get('/v3/')
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
data = jsonutils.loads(resp.body)
|
||||
expected = v3_VERSION_RESPONSE
|
||||
self._paste_in_port(expected['version'], 'http://localhost/v3/')
|
||||
self.assertEqual(data, expected)
|
||||
|
||||
def test_v2_disabled(self):
|
||||
self.stubs.Set(controllers, '_VERSIONS', ['v3'])
|
||||
client = self.client(self.public_app)
|
||||
@ -331,6 +376,10 @@ vnd.openstack.identity-v3+xml"/>
|
||||
self.public_app = self.loadapp('keystone', 'main')
|
||||
self.admin_app = self.loadapp('keystone', 'admin')
|
||||
|
||||
self.config_fixture.config(
|
||||
public_endpoint='http://localhost:%(public_port)d',
|
||||
admin_endpoint='http://localhost:%(admin_port)d')
|
||||
|
||||
fixture = self.useFixture(moxstubout.MoxStubout())
|
||||
self.stubs = fixture.stubs
|
||||
|
||||
@ -355,6 +404,14 @@ vnd.openstack.identity-v3+xml"/>
|
||||
expected = self.VERSIONS_RESPONSE % dict(port=CONF.admin_port)
|
||||
self.assertThat(data, matchers.XMLEquals(expected))
|
||||
|
||||
def test_use_site_url_if_endpoint_unset(self):
|
||||
client = self.client(self.public_app)
|
||||
resp = client.get('/', headers=self.REQUEST_HEADERS)
|
||||
self.assertEqual(resp.status_int, 300)
|
||||
data = resp.body
|
||||
expected = self.VERSIONS_RESPONSE % dict(port=CONF.public_port)
|
||||
self.assertThat(data, matchers.XMLEquals(expected))
|
||||
|
||||
def test_public_version_v2(self):
|
||||
client = self.client(self.public_app)
|
||||
resp = client.get('/v2.0/', headers=self.REQUEST_HEADERS)
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
import gettext
|
||||
import socket
|
||||
import uuid
|
||||
|
||||
from babel import localedata
|
||||
import mock
|
||||
@ -114,6 +115,13 @@ class ApplicationTest(BaseWSGITest):
|
||||
resp = wsgi.render_exception(e)
|
||||
self.assertEqual(resp.status_int, 401)
|
||||
|
||||
def test_render_exception_host(self):
|
||||
e = exception.Unauthorized(message=u'\u7f51\u7edc')
|
||||
context = {'host_url': 'http://%s:5000' % uuid.uuid4().hex}
|
||||
resp = wsgi.render_exception(e, context=context)
|
||||
|
||||
self.assertEqual(resp.status_int, 401)
|
||||
|
||||
|
||||
class ExtensionRouterTest(BaseWSGITest):
|
||||
def test_extensionrouter_local_config(self):
|
||||
|
@ -53,13 +53,13 @@ class TrustV3(controller.V3Controller):
|
||||
member_name = "trust"
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, path=None):
|
||||
def base_url(cls, context, path=None):
|
||||
"""Construct a path and pass it to V3Controller.base_url method."""
|
||||
|
||||
# NOTE(stevemar): Overriding path to /OS-TRUST/trusts so that
|
||||
# V3Controller.base_url handles setting the self link correctly.
|
||||
path = '/OS-TRUST/' + cls.collection_name
|
||||
return controller.V3Controller.base_url(path=path)
|
||||
return super(TrustV3, cls).base_url(context, path=path)
|
||||
|
||||
def _get_user_id(self, context):
|
||||
if 'token_id' in context:
|
||||
@ -99,7 +99,7 @@ class TrustV3(controller.V3Controller):
|
||||
trust_full_roles.append(full_role)
|
||||
trust['roles'] = trust_full_roles
|
||||
trust['roles_links'] = {
|
||||
'self': (self.base_url() + "/%s/roles" % trust['id']),
|
||||
'self': (self.base_url(context) + "/%s/roles" % trust['id']),
|
||||
'next': None,
|
||||
'previous': None}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user