Implements token expiration handling

This implements handling of token expiration. Once the token is expired,
this will request automatically for a new one.

A special case is introduced if the user specified a token when the client
is initialized: this is the auth_token_from_user. In this case, we can't
know the expiration date, so we just assume it will never expire and don't
handle it ourself.

Change-Id: I3771ff5d669da015d4aa259de422c5d81aed3eb4
Signed-off-by: Julien Danjou <julien@danjou.info>
This commit is contained in:
Julien Danjou
2013-01-30 18:07:29 +01:00
parent 92bf8a7af9
commit 0173560864
3 changed files with 73 additions and 7 deletions

View File

@@ -71,7 +71,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)
@@ -86,7 +85,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:
@@ -98,7 +96,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
@@ -129,6 +129,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.
@@ -168,7 +185,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,
@@ -227,7 +249,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:

View File

@@ -147,7 +147,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]

View File

@@ -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'