From e2f1845549258b1e01adca0f3979b9f1c0f475bf Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Thu, 17 Jul 2014 13:25:37 +1000 Subject: Control identity plugin reauthentication Identity plugins will by default re-authenticate themselves if they are about to expire. This is generally correct however there are times where this re-authentication doesn't make sense and we should be able to prevent it. Closes-Bug: #1352051 Change-Id: I66b50b1e650501e7f076139895473e8d1791ce27 --- keystoneclient/auth/identity/base.py | 29 +++++++++- keystoneclient/auth/identity/v2.py | 9 ++- keystoneclient/auth/identity/v3.py | 9 ++- keystoneclient/tests/auth/test_identity_common.py | 68 +++++++++++++++++------ 4 files changed, 91 insertions(+), 24 deletions(-) diff --git a/keystoneclient/auth/identity/base.py b/keystoneclient/auth/identity/base.py index fb5ed7a..ffdee4d 100644 --- a/keystoneclient/auth/identity/base.py +++ b/keystoneclient/auth/identity/base.py @@ -34,12 +34,14 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): username=None, password=None, token=None, - trust_id=None): + trust_id=None, + reauthenticate=True): super(BaseIdentityPlugin, self).__init__() self.auth_url = auth_url self.auth_ref = None + self.reauthenticate = reauthenticate self._endpoint_cache = {} @@ -81,6 +83,28 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): """ return self.get_access(session).auth_token + def _needs_reauthenticate(self): + """Return if the existing token needs to be re-authenticated. + + The token should be refreshed if it is about to expire. + + :returns: True if the plugin should fetch a new token. False otherwise. + """ + if not self.auth_ref: + # authentication was never fetched. + return True + + if not self.reauthenticate: + # don't re-authenticate if it has been disallowed. + return False + + if self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS): + # if it's about to expire we should re-authenticate now. + return True + + # otherwise it's fine and use the existing one. + return False + def get_access(self, session, **kwargs): """Fetch or return a current AccessInfo object. @@ -91,8 +115,7 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): :returns AccessInfo: Valid AccessInfo """ - if (not self.auth_ref or - self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS)): + if self._needs_reauthenticate(): self.auth_ref = self.get_auth_ref(session) return self.auth_ref diff --git a/keystoneclient/auth/identity/v2.py b/keystoneclient/auth/identity/v2.py index ad93dba..72f6259 100644 --- a/keystoneclient/auth/identity/v2.py +++ b/keystoneclient/auth/identity/v2.py @@ -43,15 +43,20 @@ class Auth(base.BaseIdentityPlugin): def __init__(self, auth_url, trust_id=None, tenant_id=None, - tenant_name=None): + tenant_name=None, + reauthenticate=True): """Construct an Identity V2 Authentication Plugin. :param string auth_url: Identity service endpoint for authorization. :param string trust_id: Trust ID for trust scoping. :param string tenant_id: Tenant ID for project scoping. :param string tenant_name: Tenant name for project scoping. + :param bool reauthenticate: Allow fetching a new token if the current + one is going to expire. + (optional) default True """ - super(Auth, self).__init__(auth_url=auth_url) + super(Auth, self).__init__(auth_url=auth_url, + reauthenticate=reauthenticate) self.trust_id = trust_id self.tenant_id = tenant_id diff --git a/keystoneclient/auth/identity/v3.py b/keystoneclient/auth/identity/v3.py index 010c80d..b385ba6 100644 --- a/keystoneclient/auth/identity/v3.py +++ b/keystoneclient/auth/identity/v3.py @@ -34,7 +34,8 @@ class Auth(base.BaseIdentityPlugin): project_id=None, project_name=None, project_domain_id=None, - project_domain_name=None): + project_domain_name=None, + reauthenticate=True): """Construct an Identity V3 Authentication Plugin. :param string auth_url: Identity service endpoint for authentication. @@ -46,9 +47,13 @@ class Auth(base.BaseIdentityPlugin): :param string project_name: Project name for project scoping. :param string project_domain_id: Project's domain ID for project. :param string project_domain_name: Project's domain name for project. + :param bool reauthenticate: Allow fetching a new token if the current + one is going to expire. + (optional) default True """ - super(Auth, self).__init__(auth_url=auth_url) + super(Auth, self).__init__(auth_url=auth_url, + reauthenticate=reauthenticate) self.auth_methods = auth_methods self.trust_id = trust_id diff --git a/keystoneclient/tests/auth/test_identity_common.py b/keystoneclient/tests/auth/test_identity_common.py index 33bb5c1..8c5499c 100644 --- a/keystoneclient/tests/auth/test_identity_common.py +++ b/keystoneclient/tests/auth/test_identity_common.py @@ -11,14 +11,17 @@ # under the License. import abc +import datetime import uuid import six +from keystoneclient import access from keystoneclient.auth import base from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v3 from keystoneclient import fixture +from keystoneclient.openstack.common import timeutils from keystoneclient import session from keystoneclient.tests import utils @@ -45,7 +48,7 @@ class CommonIdentityTests(object): self.stub_auth_data() @abc.abstractmethod - def create_auth_plugin(self): + def create_auth_plugin(self, **kwargs): """Create an auth plugin that makes sense for the auth data. It doesn't really matter what auth mechanism is used but it should be @@ -53,13 +56,17 @@ class CommonIdentityTests(object): """ @abc.abstractmethod - def stub_auth_data(self): - """Stub out authentication data. + def get_auth_data(self, **kwargs): + """Return fake authentication data. This should register a valid token response and ensure that the compute endpoints are set to TEST_COMPUTE_PUBLIC, _INTERNAL and _ADMIN. """ + def stub_auth_data(self, **kwargs): + token = self.get_auth_data(**kwargs) + self.stub_auth(json=token) + @abc.abstractproperty def version(self): """The API version being tested.""" @@ -177,6 +184,31 @@ class CommonIdentityTests(object): self.assertEqual(self.TEST_URL, auth_url) + def _create_expired_auth_plugin(self, **kwargs): + expires = timeutils.utcnow() - datetime.timedelta(minutes=20) + expired_token = self.get_auth_data(expires=expires) + expired_auth_ref = access.AccessInfo.factory(body=expired_token) + + body = 'SUCCESS' + self.stub_url('GET', ['path'], + base_url=self.TEST_COMPUTE_ADMIN, body=body) + + a = self.create_auth_plugin(**kwargs) + a.auth_ref = expired_auth_ref + return a + + def test_reauthenticate(self): + a = self._create_expired_auth_plugin() + expired_auth_ref = a.auth_ref + s = session.Session(auth=a) + self.assertIsNot(expired_auth_ref, a.get_access(s)) + + def test_no_reauthenticate(self): + a = self._create_expired_auth_plugin(reauthenticate=False) + expired_auth_ref = a.auth_ref + s = session.Session(auth=a) + self.assertIs(expired_auth_ref, a.get_access(s)) + class V3(CommonIdentityTests, utils.TestCase): @@ -184,8 +216,8 @@ class V3(CommonIdentityTests, utils.TestCase): def version(self): return 'v3' - def stub_auth_data(self): - token = fixture.V3Token() + def get_auth_data(self, **kwargs): + token = fixture.V3Token(**kwargs) region = 'RegionOne' svc = token.add_service('identity') @@ -197,7 +229,7 @@ class V3(CommonIdentityTests, utils.TestCase): internal=self.TEST_COMPUTE_INTERNAL, region=region) - self.stub_auth(json=token) + return token def stub_auth(self, subject_token=None, **kwargs): if not subject_token: @@ -206,10 +238,11 @@ class V3(CommonIdentityTests, utils.TestCase): kwargs.setdefault('headers', {})['X-Subject-Token'] = subject_token self.stub_url('POST', ['auth', 'tokens'], **kwargs) - def create_auth_plugin(self): - return v3.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS) + def create_auth_plugin(self, **kwargs): + kwargs.setdefault('auth_url', self.TEST_URL) + kwargs.setdefault('username', self.TEST_USER) + kwargs.setdefault('password', self.TEST_PASS) + return v3.Password(**kwargs) class V2(CommonIdentityTests, utils.TestCase): @@ -218,13 +251,14 @@ class V2(CommonIdentityTests, utils.TestCase): def version(self): return 'v2.0' - def create_auth_plugin(self): - return v2.Password(self.TEST_URL, - username=self.TEST_USER, - password=self.TEST_PASS) + def create_auth_plugin(self, **kwargs): + kwargs.setdefault('auth_url', self.TEST_URL) + kwargs.setdefault('username', self.TEST_USER) + kwargs.setdefault('password', self.TEST_PASS) + return v2.Password(**kwargs) - def stub_auth_data(self): - token = fixture.V2Token() + def get_auth_data(self, **kwargs): + token = fixture.V2Token(**kwargs) region = 'RegionOne' svc = token.add_service('identity') @@ -236,7 +270,7 @@ class V2(CommonIdentityTests, utils.TestCase): admin=self.TEST_COMPUTE_ADMIN, region=region) - self.stub_auth(json=token) + return token def stub_auth(self, **kwargs): self.stub_url('POST', ['tokens'], **kwargs) -- cgit v1.2.1