Allow passing auth plugin as a parameter

This further separates the concept of session and auth plugin so that
the session can be a completely standalone transport layer.

This is similar to how requests handles things. You can install an auth
plugin on the session object and then everything that works through the
session will be authenticated or you can specify the auth plugin per
request and only authenticate that request.

Change-Id: If4ed5af831cc35e259d9f963062261819f08a9d5
This commit is contained in:
Jamie Lennox 2014-03-28 16:16:41 +10:00
parent b24e41e1df
commit 71e6486a3e
2 changed files with 119 additions and 11 deletions

View File

@ -114,7 +114,7 @@ class Session(object):
@utils.positional(enforcement=utils.positional.WARN)
def request(self, url, method, json=None, original_ip=None,
user_agent=None, redirect=None, authenticated=None,
endpoint_filter=None, **kwargs):
endpoint_filter=None, auth=None, requests_auth=None, **kwargs):
"""Send an HTTP request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
@ -149,6 +149,14 @@ class Session(object):
endpoint to use for this request. If not
provided then URL is expected to be a
fully qualified URL. (optional)
:param auth: The auth plugin to use when authenticating this request.
This will override the plugin that is attached to the
session (if any). (optional)
:type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin`
:param requests_auth: A requests library auth plugin that cannot be
passed via kwarg because the `auth` kwarg
collides with our own auth plugins. (optional)
:type requests_auth: :class:`requests.auth.AuthBase`
:param kwargs: any other parameter that can be passed to
requests.Session.request (such as `headers`). Except:
'data' will be overwritten by the data in 'json' param.
@ -164,10 +172,10 @@ class Session(object):
headers = kwargs.setdefault('headers', dict())
if authenticated is None:
authenticated = self.auth is not None
authenticated = bool(auth or self.auth)
if authenticated:
token = self.get_token()
token = self.get_token(auth)
if not token:
raise exceptions.AuthorizationFailure("No token Available")
@ -180,7 +188,7 @@ class Session(object):
# requests. We check fully qualified here by the presence of a host.
url_data = urllib.parse.urlparse(url)
if endpoint_filter and not url_data.netloc:
base_url = self.get_endpoint(**endpoint_filter)
base_url = self.get_endpoint(auth, **endpoint_filter)
if not base_url:
raise exceptions.EndpointNotFound()
@ -210,6 +218,9 @@ class Session(object):
kwargs.setdefault('verify', self.verify)
if requests_auth:
kwargs['auth'] = requests_auth
string_parts = ['curl -i']
# NOTE(jamielennox): None means let requests do its default validation
@ -355,26 +366,45 @@ class Session(object):
original_ip=kwargs.pop('original_ip', None),
user_agent=kwargs.pop('user_agent', None))
def get_token(self):
def get_token(self, auth=None):
"""Return a token as provided by the auth plugin.
:param auth: The auth plugin to use for token. Overrides the plugin
on the session. (optional)
:type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin`
:raises AuthorizationFailure: if a new token fetch fails.
:returns string: A valid token.
"""
if not self.auth:
if not auth:
auth = self.auth
if not auth:
raise exceptions.MissingAuthPlugin("Token Required")
try:
return self.auth.get_token(self)
return auth.get_token(self)
except exceptions.HTTPError as exc:
raise exceptions.AuthorizationFailure("Authentication failure: "
"%s" % exc)
def get_endpoint(self, **kwargs):
"""Get an endpoint as provided by the auth plugin."""
if not self.auth:
def get_endpoint(self, auth=None, **kwargs):
"""Get an endpoint as provided by the auth plugin.
:param auth: The auth plugin to use for token. Overrides the plugin on
the session. (optional)
:type auth: :class:`keystoneclient.auth.base.BaseAuthPlugin`
:raises MissingAuthPlugin: if a plugin is not available.
:returns string: An endpoint if available or None.
"""
if not auth:
auth = self.auth
if not auth:
raise exceptions.MissingAuthPlugin('An auth plugin is required to '
'determine the endpoint URL.')
return self.auth.get_endpoint(self, **kwargs)
return auth.get_endpoint(self, **kwargs)

View File

@ -304,6 +304,23 @@ class AuthPlugin(base.BaseAuthPlugin):
return None
class CalledAuthPlugin(base.BaseAuthPlugin):
ENDPOINT = 'http://fakeendpoint/'
def __init__(self):
self.get_token_called = False
self.get_endpoint_called = False
def get_token(self, session):
self.get_token_called = True
return 'aToken'
def get_endpoint(self, session, **kwargs):
self.get_endpoint_called = True
return self.ENDPOINT
class SessionAuthTests(utils.TestCase):
TEST_URL = 'http://127.0.0.1:5000/'
@ -375,3 +392,64 @@ class SessionAuthTests(utils.TestCase):
sess.get, '/path',
endpoint_filter={'service_type': 'unknown',
'interface': 'public'})
@httpretty.activate
def test_passed_auth_plugin(self):
passed = CalledAuthPlugin()
sess = client_session.Session()
httpretty.register_uri(httpretty.GET,
CalledAuthPlugin.ENDPOINT + 'path',
status=200)
endpoint_filter = {'service_type': 'identity'}
# no plugin with authenticated won't work
self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path',
authenticated=True)
# no plugin with an endpoint filter won't work
self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path',
authenticated=False, endpoint_filter=endpoint_filter)
resp = sess.get('path', auth=passed, endpoint_filter=endpoint_filter)
self.assertEqual(200, resp.status_code)
self.assertTrue(passed.get_endpoint_called)
self.assertTrue(passed.get_token_called)
@httpretty.activate
def test_passed_auth_plugin_overrides(self):
fixed = CalledAuthPlugin()
passed = CalledAuthPlugin()
sess = client_session.Session(fixed)
httpretty.register_uri(httpretty.GET,
CalledAuthPlugin.ENDPOINT + 'path',
status=200)
resp = sess.get('path', auth=passed,
endpoint_filter={'service_type': 'identity'})
self.assertEqual(200, resp.status_code)
self.assertTrue(passed.get_endpoint_called)
self.assertTrue(passed.get_token_called)
self.assertFalse(fixed.get_endpoint_called)
self.assertFalse(fixed.get_token_called)
def test_requests_auth_plugin(self):
sess = client_session.Session()
requests_auth = object()
FAKE_RESP = utils.TestResponse({'status_code': 200, 'text': 'resp'})
RESP = mock.Mock(return_value=FAKE_RESP)
with mock.patch.object(sess.session, 'request', RESP) as mocked:
sess.get(self.TEST_URL, requests_auth=requests_auth)
mocked.assert_called_once_with('GET', self.TEST_URL,
headers=mock.ANY,
allow_redirects=mock.ANY,
auth=requests_auth,
verify=mock.ANY)