Merge "Change the default version discovery URLs"

This commit is contained in:
Jenkins 2014-03-25 04:31:48 +00:00 committed by Gerrit Code Review
commit e547af2286
19 changed files with 230 additions and 108 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 '

View File

@ -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:

View File

@ -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)

View File

@ -29,11 +29,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')
@ -46,7 +46,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:
@ -56,21 +56,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}
@ -135,7 +136,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
@ -147,14 +148,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
@ -164,13 +165,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}

View File

@ -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(),

View File

@ -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 = {}

View File

@ -36,6 +36,7 @@ class RevokeController(controller.V3Controller):
'links': {
'next': None,
'self': RevokeController.base_url(
context,
path=context['path']) + '/events',
'previous': None}
}

View File

@ -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': [

View File

@ -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."""

View File

@ -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'])

View File

@ -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'))

View File

@ -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:

View File

@ -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'])

View File

@ -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)

View File

@ -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):

View File

@ -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}