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
This commit is contained in:
@@ -161,7 +161,7 @@ class HTTPClient(object):
|
|||||||
self.project_domain_id = self.auth_ref.project_domain_id
|
self.project_domain_id = self.auth_ref.project_domain_id
|
||||||
self.auth_url = self.auth_ref.auth_url[0]
|
self.auth_url = self.auth_ref.auth_url[0]
|
||||||
self._management_url = self.auth_ref.management_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
|
self.trust_id = self.auth_ref.trust_id
|
||||||
if self.auth_ref.has_service_catalog():
|
if self.auth_ref.has_service_catalog():
|
||||||
self.region_name = self.auth_ref.service_catalog.region_name
|
self.region_name = self.auth_ref.service_catalog.region_name
|
||||||
@@ -223,6 +223,7 @@ class HTTPClient(object):
|
|||||||
self._endpoint = endpoint.rstrip('/')
|
self._endpoint = endpoint.rstrip('/')
|
||||||
if region_name:
|
if region_name:
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
|
self._auth_token = None
|
||||||
|
|
||||||
if not session:
|
if not session:
|
||||||
verify = cacert or True
|
verify = cacert or True
|
||||||
@@ -265,20 +266,28 @@ class HTTPClient(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def auth_token(self):
|
def auth_token(self):
|
||||||
if self.auth_token_from_user:
|
if self._auth_token:
|
||||||
return self.auth_token_from_user
|
return self._auth_token
|
||||||
if self.auth_ref:
|
if self.auth_ref:
|
||||||
if self.auth_ref.will_expire_soon(self.stale_duration):
|
if self.auth_ref.will_expire_soon(self.stale_duration):
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
return self.auth_ref.auth_token
|
return self.auth_ref.auth_token
|
||||||
|
if self.auth_token_from_user:
|
||||||
|
return self.auth_token_from_user
|
||||||
|
|
||||||
@auth_token.setter
|
@auth_token.setter
|
||||||
def auth_token(self, value):
|
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
|
@auth_token.deleter
|
||||||
def auth_token(self):
|
def auth_token(self):
|
||||||
del self.auth_token_from_user
|
self._auth_token = None
|
||||||
|
self.auth_token_from_user = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_catalog(self):
|
def service_catalog(self):
|
||||||
|
@@ -18,6 +18,7 @@ import json
|
|||||||
import httpretty
|
import httpretty
|
||||||
|
|
||||||
from keystoneclient import exceptions
|
from keystoneclient import exceptions
|
||||||
|
from keystoneclient.openstack.common import jsonutils
|
||||||
from keystoneclient.openstack.common import timeutils
|
from keystoneclient.openstack.common import timeutils
|
||||||
from keystoneclient.tests.v2_0 import utils
|
from keystoneclient.tests.v2_0 import utils
|
||||||
from keystoneclient.v2_0 import client
|
from keystoneclient.v2_0 import client
|
||||||
@@ -157,6 +158,27 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
|||||||
self.assertFalse('serviceCatalog' in cs.service_catalog.catalog)
|
self.assertFalse('serviceCatalog' in cs.service_catalog.catalog)
|
||||||
self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY)
|
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
|
@httpretty.activate
|
||||||
def test_authenticate_success_token_scoped(self):
|
def test_authenticate_success_token_scoped(self):
|
||||||
del self.TEST_REQUEST_BODY['auth']['passwordCredentials']
|
del self.TEST_REQUEST_BODY['auth']['passwordCredentials']
|
||||||
@@ -206,3 +228,45 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
|||||||
self.TEST_RESPONSE_DICT["access"]["token"]["id"])
|
self.TEST_RESPONSE_DICT["access"]["token"]["id"])
|
||||||
self.assertFalse('serviceCatalog' in cs.service_catalog.catalog)
|
self.assertFalse('serviceCatalog' in cs.service_catalog.catalog)
|
||||||
self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY)
|
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)
|
||||||
|
@@ -31,6 +31,8 @@ class UnauthenticatedTestCase(utils.TestCase):
|
|||||||
|
|
||||||
class TestCase(UnauthenticatedTestCase):
|
class TestCase(UnauthenticatedTestCase):
|
||||||
|
|
||||||
|
TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v2.0"
|
||||||
|
|
||||||
TEST_SERVICE_CATALOG = [{
|
TEST_SERVICE_CATALOG = [{
|
||||||
"endpoints": [{
|
"endpoints": [{
|
||||||
"adminURL": "http://cdn.admin-nets.local:8774/v1.0",
|
"adminURL": "http://cdn.admin-nets.local:8774/v1.0",
|
||||||
@@ -60,7 +62,7 @@ class TestCase(UnauthenticatedTestCase):
|
|||||||
"name": "glance"
|
"name": "glance"
|
||||||
}, {
|
}, {
|
||||||
"endpoints": [{
|
"endpoints": [{
|
||||||
"adminURL": "http://127.0.0.1:35357/v2.0",
|
"adminURL": TEST_ADMIN_IDENTITY_ENDPOINT,
|
||||||
"region": "RegionOne",
|
"region": "RegionOne",
|
||||||
"internalURL": "http://127.0.0.1:5000/v2.0",
|
"internalURL": "http://127.0.0.1:5000/v2.0",
|
||||||
"publicURL": "http://127.0.0.1:5000/v2.0"
|
"publicURL": "http://127.0.0.1:5000/v2.0"
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
import httpretty
|
import httpretty
|
||||||
|
|
||||||
from keystoneclient import exceptions
|
from keystoneclient import exceptions
|
||||||
|
from keystoneclient.openstack.common import jsonutils
|
||||||
from keystoneclient.tests.v3 import utils
|
from keystoneclient.tests.v3 import utils
|
||||||
from keystoneclient.v3 import client
|
from keystoneclient.v3 import client
|
||||||
|
|
||||||
@@ -224,6 +225,27 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
|||||||
self.assertFalse('catalog' in cs.service_catalog.catalog)
|
self.assertFalse('catalog' in cs.service_catalog.catalog)
|
||||||
self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY)
|
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
|
@httpretty.activate
|
||||||
def test_authenticate_success_token_domain_scoped(self):
|
def test_authenticate_success_token_domain_scoped(self):
|
||||||
ident = self.TEST_REQUEST_BODY['auth']['identity']
|
ident = self.TEST_REQUEST_BODY['auth']['identity']
|
||||||
@@ -301,3 +323,45 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
|||||||
self.TEST_RESPONSE_HEADERS["X-Subject-Token"])
|
self.TEST_RESPONSE_HEADERS["X-Subject-Token"])
|
||||||
self.assertFalse('catalog' in cs.service_catalog.catalog)
|
self.assertFalse('catalog' in cs.service_catalog.catalog)
|
||||||
self.assertRequestBodyIs(json=self.TEST_REQUEST_BODY)
|
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)
|
||||||
|
@@ -49,6 +49,8 @@ class UnauthenticatedTestCase(utils.TestCase):
|
|||||||
|
|
||||||
class TestCase(UnauthenticatedTestCase):
|
class TestCase(UnauthenticatedTestCase):
|
||||||
|
|
||||||
|
TEST_ADMIN_IDENTITY_ENDPOINT = "http://127.0.0.1:35357/v3"
|
||||||
|
|
||||||
TEST_SERVICE_CATALOG = [{
|
TEST_SERVICE_CATALOG = [{
|
||||||
"endpoints": [{
|
"endpoints": [{
|
||||||
"url": "http://cdn.admin-nets.local:8774/v1.0/",
|
"url": "http://cdn.admin-nets.local:8774/v1.0/",
|
||||||
@@ -105,7 +107,7 @@ class TestCase(UnauthenticatedTestCase):
|
|||||||
"region": "RegionOne",
|
"region": "RegionOne",
|
||||||
"interface": "internal"
|
"interface": "internal"
|
||||||
}, {
|
}, {
|
||||||
"url": "http://127.0.0.1:35357/v3",
|
"url": TEST_ADMIN_IDENTITY_ENDPOINT,
|
||||||
"region": "RegionOne",
|
"region": "RegionOne",
|
||||||
"interface": "admin"
|
"interface": "admin"
|
||||||
}],
|
}],
|
||||||
|
Reference in New Issue
Block a user