From 9cc2f7c73e92ae6c6b4fbf19272bb2f9209f45e7 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Thu, 17 Jul 2014 16:07:04 +1000 Subject: [PATCH] Allow unauthenticated discovery The default state for session requests is that if there is an auth plugin available then it should include a token in any requests. This is a problem for cases where it is the authentication plugin itself trying to do discovery (like in the case of version independent plugins) because you end up in an infinite loop. Allow controlling the authenticated parameter on discovery requests. Closes-Bug: #1359457 Change-Id: Ib5ab0a3a30fe79139b7b5dcaae698438281b6d36 --- keystoneclient/_discover.py | 12 +++++++---- keystoneclient/auth/identity/base.py | 12 ++++++++--- keystoneclient/discover.py | 11 ++++++++-- keystoneclient/tests/test_discovery.py | 29 ++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/keystoneclient/_discover.py b/keystoneclient/_discover.py index 5f5002710..7ea396f91 100644 --- a/keystoneclient/_discover.py +++ b/keystoneclient/_discover.py @@ -24,16 +24,18 @@ raw data specified in version discovery responses. import logging from keystoneclient import exceptions +from keystoneclient import utils _LOGGER = logging.getLogger(__name__) -def get_version_data(session, url): +@utils.positional() +def get_version_data(session, url, authenticated=None): """Retrieve raw version data from a url.""" headers = {'Accept': 'application/json'} - resp = session.get(url, headers=headers) + resp = session.get(url, headers=headers, authenticated=authenticated) try: body_resp = resp.json() @@ -131,8 +133,10 @@ class Discover(object): DEPRECATED_STATUSES = ('deprecated',) EXPERIMENTAL_STATUSES = ('experimental',) - def __init__(self, session, url): - self._data = get_version_data(session, url) + @utils.positional() + def __init__(self, session, url, authenticated=None): + self._data = get_version_data(session, url, + authenticated=authenticated) def raw_version_data(self, allow_experimental=False, allow_deprecated=True, allow_unknown=False): diff --git a/keystoneclient/auth/identity/base.py b/keystoneclient/auth/identity/base.py index 6ee7774b3..4b02f944b 100644 --- a/keystoneclient/auth/identity/base.py +++ b/keystoneclient/auth/identity/base.py @@ -19,6 +19,7 @@ import six from keystoneclient import _discover from keystoneclient.auth import base from keystoneclient import exceptions +from keystoneclient import utils LOG = logging.getLogger(__name__) @@ -192,7 +193,7 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): return url try: - disc = self.get_discovery(session, url) + disc = self.get_discovery(session, url, authenticated=False) except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): @@ -206,7 +207,8 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): return url - def get_discovery(self, session, url): + @utils.positional() + def get_discovery(self, session, url, authenticated=None): """Return the discovery object for a URL. Check the session and the plugin cache to see if we have already @@ -218,6 +220,9 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): :param Session session: A session object to discover with. :param str url: The url to lookup. + :param bool authenticated: Include a token in the discovery call. + (optional) Defaults to None (use a token + if a plugin is installed). :raises: DiscoveryFailure if for some reason the lookup fails. :raises: HttpError An error from an invalid HTTP response. @@ -241,7 +246,8 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): if disc: break else: - disc = _discover.Discover(session, url) + disc = _discover.Discover(session, url, + authenticated=authenticated) self._endpoint_cache[url] = disc session_endpoint_cache[url] = disc diff --git a/keystoneclient/discover.py b/keystoneclient/discover.py index 7d8e1edf9..07de97dd4 100644 --- a/keystoneclient/discover.py +++ b/keystoneclient/discover.py @@ -17,6 +17,7 @@ import six from keystoneclient import _discover from keystoneclient import exceptions from keystoneclient import session as client_session +from keystoneclient import utils from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client @@ -44,7 +45,8 @@ class Discover(_discover.Discover): operates upon the data that was retrieved. """ - def __init__(self, session=None, **kwargs): + @utils.positional(2) + def __init__(self, session=None, authenticated=None, **kwargs): """Construct a new discovery object. The connection parameters associated with this method are the same @@ -98,6 +100,10 @@ class Discover(_discover.Discover): service. default: False (optional) DEPRECATED: use the session object. This is ignored if a session is provided. + :param bool authenticated: Should a token be used to perform the + initial discovery operations. + default: None (attach a token if an auth + plugin is available). """ if not session: @@ -121,7 +127,8 @@ class Discover(_discover.Discover): 'auth_url or endpoint') self._client_kwargs = kwargs - super(Discover, self).__init__(session, url) + super(Discover, self).__init__(session, url, + authenticated=authenticated) def available_versions(self, **kwargs): """Return a list of identity APIs available on the server and the data diff --git a/keystoneclient/tests/test_discovery.py b/keystoneclient/tests/test_discovery.py index fc0f5518c..10f1c2f21 100644 --- a/keystoneclient/tests/test_discovery.py +++ b/keystoneclient/tests/test_discovery.py @@ -10,15 +10,19 @@ # License for the specific language governing permissions and limitations # under the License. +import uuid + import six from testtools import matchers from keystoneclient import _discover +from keystoneclient.auth import token_endpoint from keystoneclient import client from keystoneclient import discover from keystoneclient import exceptions from keystoneclient import fixture from keystoneclient.openstack.common import jsonutils +from keystoneclient import session from keystoneclient.tests import utils from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client @@ -547,6 +551,31 @@ class ClientDiscoveryTests(utils.TestCase): self.assertRaises(exceptions.DiscoveryFailure, disc.create_client, version=(3, 0)) + def _do_discovery_call(self, token=None, **kwargs): + self.requests.register_uri('GET', BASE_URL, status_code=300, + text=V3_VERSION_LIST) + + if not token: + token = uuid.uuid4().hex + + url = 'http://testurl' + a = token_endpoint.Token(url, token) + s = session.Session(auth=a) + + # will default to true as there is a plugin on the session + discover.Discover(s, auth_url=BASE_URL, **kwargs) + + self.assertEqual(BASE_URL, self.requests.last_request.url) + + def test_setting_authenticated_true(self): + token = uuid.uuid4().hex + self._do_discovery_call(token) + self.assertRequestHeaderEqual('X-Auth-Token', token) + + def test_setting_authenticated_false(self): + self._do_discovery_call(authenticated=False) + self.assertNotIn('X-Auth-Token', self.requests.last_request.headers) + class DiscoverQueryTests(utils.TestCase):