diff --git a/keystoneclient/client.py b/keystoneclient/client.py index 84307cf8e..a016a5128 100644 --- a/keystoneclient/client.py +++ b/keystoneclient/client.py @@ -67,7 +67,6 @@ class HTTPClient(object): self.tenant_id = None self.tenant_name = None self.auth_url = None - self.auth_token = None self.management_url = None if timeout is not None: self.timeout = float(timeout) @@ -82,7 +81,6 @@ class HTTPClient(object): self.tenant_name = self.auth_ref.tenant_name 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 # allow override of the auth_ref defaults from explicit # values provided to the client if username: @@ -94,7 +92,9 @@ class HTTPClient(object): if auth_url: self.auth_url = auth_url.rstrip('/') if token: - self.auth_token = token + self.auth_token_from_user = token + else: + self.auth_token_from_user = None if endpoint: self.management_url = endpoint.rstrip('/') self.password = password @@ -126,6 +126,23 @@ class HTTPClient(object): self.stale_duration = stale_duration or access.STALE_TOKEN_DURATION self.stale_duration = int(self.stale_duration) + @property + def auth_token(self): + if self.auth_token_from_user: + return self.auth_token_from_user + if self.auth_ref: + if self.auth_ref.will_expire_soon(self.stale_duration): + self.authenticate() + return self.auth_ref.auth_token + + @auth_token.setter + def auth_token(self, value): + self.auth_token_from_user = value + + @auth_token.deleter + def auth_token(selef): + del self.auth_token_from_user + def authenticate(self, username=None, password=None, tenant_name=None, tenant_id=None, auth_url=None, token=None): """ Authenticate user. @@ -165,7 +182,12 @@ class HTTPClient(object): password = password or self.password tenant_name = tenant_name or self.tenant_name tenant_id = tenant_id or self.tenant_id - token = token or self.auth_token + + if not token: + token = self.auth_token_from_user + if (not token and self.auth_ref + and not self.auth_ref.will_expire_soon(self.stale_duration)): + token = self.auth_ref.auth_token (keyring_key, auth_ref) = self.get_auth_ref_from_keyring(auth_url, username, @@ -224,7 +246,7 @@ class HTTPClient(object): keyring_key) if auth_ref: auth_ref = pickle.loads(auth_ref) - if auth_ref.will_expire_soon(self.stale_duration): + if self.auth_ref.will_expire_soon(self.stale_duration): # token has expired, don't use it auth_ref = None except Exception as e: diff --git a/keystoneclient/v2_0/client.py b/keystoneclient/v2_0/client.py index 77c096c94..03cb8f2e5 100644 --- a/keystoneclient/v2_0/client.py +++ b/keystoneclient/v2_0/client.py @@ -146,7 +146,6 @@ class Client(client.HTTPClient): # if we got a response without a service catalog, set the local # list of tenants for introspection, and leave to client user # to determine what to do. Otherwise, load up the service catalog - self.auth_token = self.auth_ref.auth_token if self.auth_ref.scoped: if self.management_url is None and self.auth_ref.management_url: self.management_url = self.auth_ref.management_url[0] diff --git a/tests/v2_0/test_auth.py b/tests/v2_0/test_auth.py index cd9caa56d..0c0af9984 100644 --- a/tests/v2_0/test_auth.py +++ b/tests/v2_0/test_auth.py @@ -1,10 +1,12 @@ import copy +from datetime import timedelta import json import requests from keystoneclient.v2_0 import client from keystoneclient import exceptions +from keystoneclient.openstack.common import timeutils from tests import utils @@ -14,7 +16,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): self.TEST_RESPONSE_DICT = { "access": { "token": { - "expires": "12345", + "expires": "2999-01-01T00:00:10Z", "id": self.TEST_TOKEN, "tenant": { "id": self.TEST_TENANT_ID @@ -40,6 +42,49 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): 'User-Agent': 'python-keystoneclient', } + def test_authenticate_success_expired(self): + # Build an expired token + self.TEST_RESPONSE_DICT['access']['token']['expires'] = \ + (timeutils.utcnow() - timedelta(1)).isoformat() + resp = utils.TestResponse({ + "status_code": 200, + "text": json.dumps(self.TEST_RESPONSE_DICT), + }) + + kwargs = copy.copy(self.TEST_REQUEST_BASE) + kwargs['headers'] = self.TEST_REQUEST_HEADERS + kwargs['data'] = json.dumps(self.TEST_REQUEST_BODY) + requests.request('POST', + self.TEST_URL + "/tokens", + **kwargs).AndReturn((resp)) + self.mox.ReplayAll() + + cs = client.Client(tenant_id=self.TEST_TENANT_ID, + auth_url=self.TEST_URL, + username=self.TEST_USER, + password=self.TEST_TOKEN) + self.assertEqual(cs.management_url, + self.TEST_RESPONSE_DICT["access"]["serviceCatalog"][3] + ['endpoints'][0]["adminURL"]) + + # Build a new response + self.mox.ResetAll() + TEST_TOKEN = "abcdef" + self.TEST_RESPONSE_DICT['access']['token']['expires'] = \ + "2999-01-01T00:00:10Z" + self.TEST_RESPONSE_DICT['access']['token']['id'] = TEST_TOKEN + + resp = utils.TestResponse({ + "status_code": 200, + "text": json.dumps(self.TEST_RESPONSE_DICT), + }) + requests.request('POST', + self.TEST_URL + "/tokens", + **kwargs).AndReturn((resp)) + self.mox.ReplayAll() + + self.assertEqual(cs.auth_token, TEST_TOKEN) + def test_authenticate_failure(self): _auth = 'auth' _cred = 'passwordCredentials'