Allow identity plugins to discover relative version urls

When using a reverse proxy or TLS terminator it can be really hard to
correctly specify a URL in version discovery that is correct for all
situations.

Make it so that a relative URL in a discovery document is joined to the
queried URL to always return a fully qualified URL to the identity
plugins.

Change-Id: Ia08538ccf00c9063dc0d284c5ece9a969c15500a
Closes-Bug: #1614304
This commit is contained in:
Jamie Lennox 2016-08-18 09:57:37 +10:00
parent 7f13a4f570
commit fe7ea40ea5
2 changed files with 84 additions and 4 deletions

View File

@ -18,6 +18,7 @@ import threading
from positional import positional
import six
from six.moves import urllib
from keystoneauth1 import _utils as utils
from keystoneauth1 import access
@ -239,7 +240,14 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
'Fallback to using that endpoint as the base url.',
url)
else:
url = disc.url_for(version, **allow)
# NOTE(jamielennox): urljoin allows the url to be relative or even
# protocol-less. The additional trailing '/' make urljoin respect
# the current path as canonical even if the url doesn't include it.
# for example a "v2" path from http://host/admin should resolve as
# http://host/admin/v2 where it would otherwise be host/v2.
# This has no effect on absolute urls returned from url_for.
url_for = disc.url_for(version, **allow)
url = urllib.parse.urljoin(hacked_url.rstrip('/') + '/', url_for)
return url

View File

@ -31,9 +31,10 @@ class CommonIdentityTests(object):
TEST_ROOT_URL = 'http://127.0.0.1:5000/'
TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/'
TEST_COMPUTE_PUBLIC = 'http://nova/novapi/public'
TEST_COMPUTE_INTERNAL = 'http://nova/novapi/internal'
TEST_COMPUTE_ADMIN = 'http://nova/novapi/admin'
TEST_COMPUTE_BASE = 'http://nova'
TEST_COMPUTE_PUBLIC = TEST_COMPUTE_BASE + '/novapi/public'
TEST_COMPUTE_INTERNAL = TEST_COMPUTE_BASE + '/novapi/internal'
TEST_COMPUTE_ADMIN = TEST_COMPUTE_BASE + '/novapi/admin'
TEST_PASS = uuid.uuid4().hex
@ -181,6 +182,77 @@ class CommonIdentityTests(object):
self.assertEqual(200, resp.status_code)
self.assertEqual(body, resp.text)
def test_discovering_with_relative_link(self):
# need to construct list this way for relative
disc = fixture.DiscoveryList(v2=False, v3=False)
disc.add_v2('v2.0')
disc.add_v3('v3')
self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc)
a = self.create_auth_plugin()
s = session.Session(auth=a)
endpoint_v2 = s.get_endpoint(service_type='compute',
interface='admin',
version=(2, 0))
endpoint_v3 = s.get_endpoint(service_type='compute',
interface='admin',
version=(3, 0))
self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2)
self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', endpoint_v3)
def test_discovering_with_relative_anchored_link(self):
# need to construct list this way for relative
disc = fixture.DiscoveryList(v2=False, v3=False)
disc.add_v2('/v2.0')
disc.add_v3('/v3')
self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc)
a = self.create_auth_plugin()
s = session.Session(auth=a)
endpoint_v2 = s.get_endpoint(service_type='compute',
interface='admin',
version=(2, 0))
endpoint_v3 = s.get_endpoint(service_type='compute',
interface='admin',
version=(3, 0))
# by the nature of urljoin a relative link with a /path gets joined
# back to the root.
self.assertEqual(self.TEST_COMPUTE_BASE + '/v2.0', endpoint_v2)
self.assertEqual(self.TEST_COMPUTE_BASE + '/v3', endpoint_v3)
def test_discovering_with_protocol_relative(self):
# strip up to and including the : leaving //host/path
path = self.TEST_COMPUTE_ADMIN[self.TEST_COMPUTE_ADMIN.find(':') + 1:]
disc = fixture.DiscoveryList(v2=False, v3=False)
disc.add_v2(path + '/v2.0')
disc.add_v3(path + '/v3')
self.stub_url('GET', [], base_url=self.TEST_COMPUTE_ADMIN, json=disc)
a = self.create_auth_plugin()
s = session.Session(auth=a)
endpoint_v2 = s.get_endpoint(service_type='compute',
interface='admin',
version=(2, 0))
endpoint_v3 = s.get_endpoint(service_type='compute',
interface='admin',
version=(3, 0))
# ensures that the http is carried over from the lookup url
self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v2.0', endpoint_v2)
self.assertEqual(self.TEST_COMPUTE_ADMIN + '/v3', endpoint_v3)
def test_asking_for_auth_endpoint_ignores_checks(self):
a = self.create_auth_plugin()
s = session.Session(auth=a)