From 35aed518a9a8c0049512910219ca6fea4f35f2b8 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Fri, 22 Nov 2013 11:14:49 +1000 Subject: [PATCH] Correctly handle auth_url/token authentication Previously the client assumed that if a user passed a token then this token should be used for everything. This assumption is correct for the endpoint/token case but not in the auth_url/token case where you will want to fetch a new token. This is needed in the case where you want to use an existing token to fetch a token that is re-scoped or activate a trust. There are still problems such as if you use auth_url/token authentication then when the token expires it will try to refresh it, but authenticating with a token will not extend the token expiry. Closes-Bug: #1257541 Change-Id: I1c35600ca5437da44071dcea5361bfb42f6b72a3 --- keystoneclient/httpclient.py | 19 ++++++-- keystoneclient/tests/v2_0/test_auth.py | 64 ++++++++++++++++++++++++++ keystoneclient/tests/v2_0/utils.py | 4 +- keystoneclient/tests/v3/test_auth.py | 64 ++++++++++++++++++++++++++ keystoneclient/tests/v3/utils.py | 4 +- 5 files changed, 148 insertions(+), 7 deletions(-) diff --git a/keystoneclient/httpclient.py b/keystoneclient/httpclient.py index a0dcf24b1..9f901cd21 100644 --- a/keystoneclient/httpclient.py +++ b/keystoneclient/httpclient.py @@ -161,7 +161,7 @@ class HTTPClient(object): self.project_domain_id = self.auth_ref.project_domain_id self.auth_url = self.auth_ref.auth_url[0] self._management_url = self.auth_ref.management_url[0] - self.auth_token = self.auth_ref.auth_token + self.auth_token_from_user = self.auth_ref.auth_token self.trust_id = self.auth_ref.trust_id if self.auth_ref.has_service_catalog(): self.region_name = self.auth_ref.service_catalog.region_name @@ -223,6 +223,7 @@ class HTTPClient(object): self._endpoint = endpoint.rstrip('/') if region_name: self.region_name = region_name + self._auth_token = None if not session: verify = cacert or True @@ -265,20 +266,28 @@ class HTTPClient(object): @property def auth_token(self): - if self.auth_token_from_user: - return self.auth_token_from_user + if self._auth_token: + return self._auth_token if self.auth_ref: if self.auth_ref.will_expire_soon(self.stale_duration): self.authenticate() return self.auth_ref.auth_token + if self.auth_token_from_user: + return self.auth_token_from_user @auth_token.setter def auth_token(self, value): - self.auth_token_from_user = value + """Override the auth_token. + + If an application sets auth_token explicitly then it will always be + used and override any past or future retrieved token. + """ + self._auth_token = value @auth_token.deleter def auth_token(self): - del self.auth_token_from_user + self._auth_token = None + self.auth_token_from_user = None @property def service_catalog(self): diff --git a/keystoneclient/tests/v2_0/test_auth.py b/keystoneclient/tests/v2_0/test_auth.py index 94333ea56..98a3380e7 100644 --- a/keystoneclient/tests/v2_0/test_auth.py +++ b/keystoneclient/tests/v2_0/test_auth.py @@ -18,6 +18,7 @@ import json import httpretty from keystoneclient import exceptions +from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import timeutils from keystoneclient.tests.v2_0 import utils from keystoneclient.v2_0 import client @@ -157,6 +158,27 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): self.assertFalse('serviceCatalog' in cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) + @httpretty.activate + def test_auth_url_token_authentication(self): + fake_token = 'fake_token' + fake_url = '/fake-url' + fake_resp = {'result': True} + + self.stub_auth(json=self.TEST_RESPONSE_DICT) + self.stub_url('GET', [fake_url], json=fake_resp, + base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) + + cl = client.Client(auth_url=self.TEST_URL, + token=fake_token) + body = jsonutils.loads(httpretty.last_request().body) + self.assertEqual(body['auth']['token']['id'], fake_token) + + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + self.TEST_TOKEN) + @httpretty.activate def test_authenticate_success_token_scoped(self): del self.TEST_REQUEST_BODY['auth']['passwordCredentials'] @@ -206,3 +228,45 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): self.TEST_RESPONSE_DICT["access"]["token"]["id"]) self.assertFalse('serviceCatalog' in cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) + + @httpretty.activate + def test_allow_override_of_auth_token(self): + fake_url = '/fake-url' + fake_token = 'fake_token' + fake_resp = {'result': True} + + self.stub_auth(json=self.TEST_RESPONSE_DICT) + self.stub_url('GET', [fake_url], json=fake_resp, + base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) + + cl = client.Client(username='exampleuser', + password='password', + tenant_name='exampleproject', + auth_url=self.TEST_URL) + + self.assertEqual(cl.auth_token, self.TEST_TOKEN) + + # the token returned from the authentication will be used + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + self.TEST_TOKEN) + + # then override that token and the new token shall be used + cl.auth_token = fake_token + + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + fake_token) + + # if we clear that overriden token then we fall back to the original + del cl.auth_token + + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + self.TEST_TOKEN) diff --git a/keystoneclient/tests/v2_0/utils.py b/keystoneclient/tests/v2_0/utils.py index 6716527fd..238543f95 100644 --- a/keystoneclient/tests/v2_0/utils.py +++ b/keystoneclient/tests/v2_0/utils.py @@ -31,6 +31,8 @@ class UnauthenticatedTestCase(utils.TestCase): class TestCase(UnauthenticatedTestCase): + TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v2.0" + TEST_SERVICE_CATALOG = [{ "endpoints": [{ "adminURL": "http://cdn.admin-nets.local:8774/v1.0", @@ -60,7 +62,7 @@ class TestCase(UnauthenticatedTestCase): "name": "glance" }, { "endpoints": [{ - "adminURL": "http://127.0.0.1:35357/v2.0", + "adminURL": TEST_ADMIN_IDENTITY_ENDPOINT, "region": "RegionOne", "internalURL": "http://127.0.0.1:5000/v2.0", "publicURL": "http://127.0.0.1:5000/v2.0" diff --git a/keystoneclient/tests/v3/test_auth.py b/keystoneclient/tests/v3/test_auth.py index c7149a85a..9c0297064 100644 --- a/keystoneclient/tests/v3/test_auth.py +++ b/keystoneclient/tests/v3/test_auth.py @@ -15,6 +15,7 @@ import httpretty from keystoneclient import exceptions +from keystoneclient.openstack.common import jsonutils from keystoneclient.tests.v3 import utils from keystoneclient.v3 import client @@ -224,6 +225,27 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): self.assertFalse('catalog' in cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) + @httpretty.activate + def test_auth_url_token_authentication(self): + fake_token = 'fake_token' + fake_url = '/fake-url' + fake_resp = {'result': True} + + self.stub_auth(json=self.TEST_RESPONSE_DICT) + self.stub_url('GET', [fake_url], json=fake_resp, + base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) + + cl = client.Client(auth_url=self.TEST_URL, + token=fake_token) + body = jsonutils.loads(httpretty.last_request().body) + self.assertEqual(body['auth']['identity']['token']['id'], fake_token) + + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + self.TEST_TOKEN) + @httpretty.activate def test_authenticate_success_token_domain_scoped(self): ident = self.TEST_REQUEST_BODY['auth']['identity'] @@ -301,3 +323,45 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): self.TEST_RESPONSE_HEADERS["X-Subject-Token"]) self.assertFalse('catalog' in cs.service_catalog.catalog) self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY) + + @httpretty.activate + def test_allow_override_of_auth_token(self): + fake_url = '/fake-url' + fake_token = 'fake_token' + fake_resp = {'result': True} + + self.stub_auth(json=self.TEST_RESPONSE_DICT) + self.stub_url('GET', [fake_url], json=fake_resp, + base_url=self.TEST_ADMIN_IDENTITY_ENDPOINT) + + cl = client.Client(username='exampleuser', + password='password', + tenant_name='exampleproject', + auth_url=self.TEST_URL) + + self.assertEqual(cl.auth_token, self.TEST_TOKEN) + + # the token returned from the authentication will be used + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + self.TEST_TOKEN) + + # then override that token and the new token shall be used + cl.auth_token = fake_token + + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + fake_token) + + # if we clear that overriden token then we fall back to the original + del cl.auth_token + + resp, body = cl.get(fake_url) + self.assertEqual(fake_resp, body) + + self.assertEqual(httpretty.last_request().headers.get('X-Auth-Token'), + self.TEST_TOKEN) diff --git a/keystoneclient/tests/v3/utils.py b/keystoneclient/tests/v3/utils.py index 182e947c3..2965860b3 100644 --- a/keystoneclient/tests/v3/utils.py +++ b/keystoneclient/tests/v3/utils.py @@ -49,6 +49,8 @@ class UnauthenticatedTestCase(utils.TestCase): class TestCase(UnauthenticatedTestCase): + TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v3" + TEST_SERVICE_CATALOG = [{ "endpoints": [{ "url": "http://cdn.admin-nets.local:8774/v1.0/", @@ -105,7 +107,7 @@ class TestCase(UnauthenticatedTestCase): "region": "RegionOne", "interface": "internal" }, { - "url": "http://127.0.0.1:35357/v3", + "url": TEST_ADMIN_IDENTITY_ENDPOINT, "region": "RegionOne", "interface": "admin" }],