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
This commit is contained in:
@@ -34,12 +34,14 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
|
|||||||
username=None,
|
username=None,
|
||||||
password=None,
|
password=None,
|
||||||
token=None,
|
token=None,
|
||||||
trust_id=None):
|
trust_id=None,
|
||||||
|
reauthenticate=True):
|
||||||
|
|
||||||
super(BaseIdentityPlugin, self).__init__()
|
super(BaseIdentityPlugin, self).__init__()
|
||||||
|
|
||||||
self.auth_url = auth_url
|
self.auth_url = auth_url
|
||||||
self.auth_ref = None
|
self.auth_ref = None
|
||||||
|
self.reauthenticate = reauthenticate
|
||||||
|
|
||||||
self._endpoint_cache = {}
|
self._endpoint_cache = {}
|
||||||
|
|
||||||
@@ -81,6 +83,28 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
|
|||||||
"""
|
"""
|
||||||
return self.get_access(session).auth_token
|
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):
|
def get_access(self, session, **kwargs):
|
||||||
"""Fetch or return a current AccessInfo object.
|
"""Fetch or return a current AccessInfo object.
|
||||||
|
|
||||||
@@ -91,8 +115,7 @@ class BaseIdentityPlugin(base.BaseAuthPlugin):
|
|||||||
|
|
||||||
:returns AccessInfo: Valid AccessInfo
|
:returns AccessInfo: Valid AccessInfo
|
||||||
"""
|
"""
|
||||||
if (not self.auth_ref or
|
if self._needs_reauthenticate():
|
||||||
self.auth_ref.will_expire_soon(self.MIN_TOKEN_LIFE_SECONDS)):
|
|
||||||
self.auth_ref = self.get_auth_ref(session)
|
self.auth_ref = self.get_auth_ref(session)
|
||||||
|
|
||||||
return self.auth_ref
|
return self.auth_ref
|
||||||
|
@@ -43,15 +43,20 @@ class Auth(base.BaseIdentityPlugin):
|
|||||||
def __init__(self, auth_url,
|
def __init__(self, auth_url,
|
||||||
trust_id=None,
|
trust_id=None,
|
||||||
tenant_id=None,
|
tenant_id=None,
|
||||||
tenant_name=None):
|
tenant_name=None,
|
||||||
|
reauthenticate=True):
|
||||||
"""Construct an Identity V2 Authentication Plugin.
|
"""Construct an Identity V2 Authentication Plugin.
|
||||||
|
|
||||||
:param string auth_url: Identity service endpoint for authorization.
|
:param string auth_url: Identity service endpoint for authorization.
|
||||||
:param string trust_id: Trust ID for trust scoping.
|
:param string trust_id: Trust ID for trust scoping.
|
||||||
:param string tenant_id: Tenant ID for project scoping.
|
:param string tenant_id: Tenant ID for project scoping.
|
||||||
:param string tenant_name: Tenant name 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.trust_id = trust_id
|
||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
|
@@ -34,7 +34,8 @@ class Auth(base.BaseIdentityPlugin):
|
|||||||
project_id=None,
|
project_id=None,
|
||||||
project_name=None,
|
project_name=None,
|
||||||
project_domain_id=None,
|
project_domain_id=None,
|
||||||
project_domain_name=None):
|
project_domain_name=None,
|
||||||
|
reauthenticate=True):
|
||||||
"""Construct an Identity V3 Authentication Plugin.
|
"""Construct an Identity V3 Authentication Plugin.
|
||||||
|
|
||||||
:param string auth_url: Identity service endpoint for authentication.
|
: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_name: Project name for project scoping.
|
||||||
:param string project_domain_id: Project's domain ID for project.
|
:param string project_domain_id: Project's domain ID for project.
|
||||||
:param string project_domain_name: Project's domain name 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.auth_methods = auth_methods
|
||||||
self.trust_id = trust_id
|
self.trust_id = trust_id
|
||||||
|
@@ -11,14 +11,17 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from keystoneclient import access
|
||||||
from keystoneclient.auth import base
|
from keystoneclient.auth import base
|
||||||
from keystoneclient.auth.identity import v2
|
from keystoneclient.auth.identity import v2
|
||||||
from keystoneclient.auth.identity import v3
|
from keystoneclient.auth.identity import v3
|
||||||
from keystoneclient import fixture
|
from keystoneclient import fixture
|
||||||
|
from keystoneclient.openstack.common import timeutils
|
||||||
from keystoneclient import session
|
from keystoneclient import session
|
||||||
from keystoneclient.tests import utils
|
from keystoneclient.tests import utils
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ class CommonIdentityTests(object):
|
|||||||
self.stub_auth_data()
|
self.stub_auth_data()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def create_auth_plugin(self):
|
def create_auth_plugin(self, **kwargs):
|
||||||
"""Create an auth plugin that makes sense for the auth data.
|
"""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
|
It doesn't really matter what auth mechanism is used but it should be
|
||||||
@@ -53,13 +56,17 @@ class CommonIdentityTests(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def stub_auth_data(self):
|
def get_auth_data(self, **kwargs):
|
||||||
"""Stub out authentication data.
|
"""Return fake authentication data.
|
||||||
|
|
||||||
This should register a valid token response and ensure that the compute
|
This should register a valid token response and ensure that the compute
|
||||||
endpoints are set to TEST_COMPUTE_PUBLIC, _INTERNAL and _ADMIN.
|
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
|
@abc.abstractproperty
|
||||||
def version(self):
|
def version(self):
|
||||||
"""The API version being tested."""
|
"""The API version being tested."""
|
||||||
@@ -177,6 +184,31 @@ class CommonIdentityTests(object):
|
|||||||
|
|
||||||
self.assertEqual(self.TEST_URL, auth_url)
|
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):
|
class V3(CommonIdentityTests, utils.TestCase):
|
||||||
|
|
||||||
@@ -184,8 +216,8 @@ class V3(CommonIdentityTests, utils.TestCase):
|
|||||||
def version(self):
|
def version(self):
|
||||||
return 'v3'
|
return 'v3'
|
||||||
|
|
||||||
def stub_auth_data(self):
|
def get_auth_data(self, **kwargs):
|
||||||
token = fixture.V3Token()
|
token = fixture.V3Token(**kwargs)
|
||||||
region = 'RegionOne'
|
region = 'RegionOne'
|
||||||
|
|
||||||
svc = token.add_service('identity')
|
svc = token.add_service('identity')
|
||||||
@@ -197,7 +229,7 @@ class V3(CommonIdentityTests, utils.TestCase):
|
|||||||
internal=self.TEST_COMPUTE_INTERNAL,
|
internal=self.TEST_COMPUTE_INTERNAL,
|
||||||
region=region)
|
region=region)
|
||||||
|
|
||||||
self.stub_auth(json=token)
|
return token
|
||||||
|
|
||||||
def stub_auth(self, subject_token=None, **kwargs):
|
def stub_auth(self, subject_token=None, **kwargs):
|
||||||
if not subject_token:
|
if not subject_token:
|
||||||
@@ -206,10 +238,11 @@ class V3(CommonIdentityTests, utils.TestCase):
|
|||||||
kwargs.setdefault('headers', {})['X-Subject-Token'] = subject_token
|
kwargs.setdefault('headers', {})['X-Subject-Token'] = subject_token
|
||||||
self.stub_url('POST', ['auth', 'tokens'], **kwargs)
|
self.stub_url('POST', ['auth', 'tokens'], **kwargs)
|
||||||
|
|
||||||
def create_auth_plugin(self):
|
def create_auth_plugin(self, **kwargs):
|
||||||
return v3.Password(self.TEST_URL,
|
kwargs.setdefault('auth_url', self.TEST_URL)
|
||||||
username=self.TEST_USER,
|
kwargs.setdefault('username', self.TEST_USER)
|
||||||
password=self.TEST_PASS)
|
kwargs.setdefault('password', self.TEST_PASS)
|
||||||
|
return v3.Password(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class V2(CommonIdentityTests, utils.TestCase):
|
class V2(CommonIdentityTests, utils.TestCase):
|
||||||
@@ -218,13 +251,14 @@ class V2(CommonIdentityTests, utils.TestCase):
|
|||||||
def version(self):
|
def version(self):
|
||||||
return 'v2.0'
|
return 'v2.0'
|
||||||
|
|
||||||
def create_auth_plugin(self):
|
def create_auth_plugin(self, **kwargs):
|
||||||
return v2.Password(self.TEST_URL,
|
kwargs.setdefault('auth_url', self.TEST_URL)
|
||||||
username=self.TEST_USER,
|
kwargs.setdefault('username', self.TEST_USER)
|
||||||
password=self.TEST_PASS)
|
kwargs.setdefault('password', self.TEST_PASS)
|
||||||
|
return v2.Password(**kwargs)
|
||||||
|
|
||||||
def stub_auth_data(self):
|
def get_auth_data(self, **kwargs):
|
||||||
token = fixture.V2Token()
|
token = fixture.V2Token(**kwargs)
|
||||||
region = 'RegionOne'
|
region = 'RegionOne'
|
||||||
|
|
||||||
svc = token.add_service('identity')
|
svc = token.add_service('identity')
|
||||||
@@ -236,7 +270,7 @@ class V2(CommonIdentityTests, utils.TestCase):
|
|||||||
admin=self.TEST_COMPUTE_ADMIN,
|
admin=self.TEST_COMPUTE_ADMIN,
|
||||||
region=region)
|
region=region)
|
||||||
|
|
||||||
self.stub_auth(json=token)
|
return token
|
||||||
|
|
||||||
def stub_auth(self, **kwargs):
|
def stub_auth(self, **kwargs):
|
||||||
self.stub_url('POST', ['tokens'], **kwargs)
|
self.stub_url('POST', ['tokens'], **kwargs)
|
||||||
|
Reference in New Issue
Block a user