From 73e4296a389893c750f7c70a477ec828e4360197 Mon Sep 17 00:00:00 2001 From: Paulo Ewerton Date: Tue, 29 Mar 2016 19:10:42 +0000 Subject: Adding keystoneauth sessions support This patch allows authentication in swiftclient with a keystonauth session. Co-Authored-By: Tim Burke Change-Id: Ia3fd947ff619c11ff0ce474897533dcf7b49d9b3 Closes-Bug: 1518938 --- doc/source/client-api.rst | 22 ++++++++++++++++++++++ swiftclient/client.py | 35 +++++++++++++++++++++++++---------- tests/unit/test_swiftclient.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/doc/source/client-api.rst b/doc/source/client-api.rst index 5677f70..13b3056 100644 --- a/doc/source/client-api.rst +++ b/doc/source/client-api.rst @@ -18,6 +18,28 @@ version are detailed below, but are just a subset of those that can be used to successfully authenticate. These are the most common and recommended combinations. +Keystone Session +~~~~~~~~~~~~~~~~ + +.. code-block:: python + + from keystoneauth1 import session + from keystoneauth1 import v3 + + # Create a password auth plugin + auth = v3.Password(auth_url='http://127.0.0.1:5000/v3/', + username='tester', + password='testing', + user_domain_name='Default', + project_name='Default', + project_domain_name='Default') + + # Create session + session = session.Session(auth=auth) + + # Create swiftclient Connection + swift_conn = Connection(session=session) + Keystone v3 ~~~~~~~~~~~ diff --git a/swiftclient/client.py b/swiftclient/client.py index 744a876..8370764 100644 --- a/swiftclient/client.py +++ b/swiftclient/client.py @@ -609,6 +609,7 @@ def get_auth(auth_url, user, key, **kwargs): use of this network path causes no bandwidth charges but requires the client to be running on Rackspace's ServiceNet network. """ + session = kwargs.get('session', None) auth_version = kwargs.get('auth_version', '1') os_options = kwargs.get('os_options', {}) @@ -617,7 +618,14 @@ def get_auth(auth_url, user, key, **kwargs): cert = kwargs.get('cert') cert_key = kwargs.get('cert_key') timeout = kwargs.get('timeout', None) - if auth_version in AUTH_VERSIONS_V1: + + if session: + service_type = os_options.get('service_type', 'object-store') + interface = os_options.get('endpoint_type', 'public') + storage_url = session.get_endpoint(service_type=service_type, + interface=interface) + token = session.get_token() + elif auth_version in AUTH_VERSIONS_V1: storage_url, token = get_auth_1_0(auth_url, user, key, @@ -654,8 +662,8 @@ def get_auth(auth_url, user, key, **kwargs): timeout=timeout, auth_version=auth_version) else: - raise ClientException('Unknown auth_version %s specified.' - % auth_version) + raise ClientException('Unknown auth_version %s specified and no ' + 'session found.' % auth_version) # Override storage url, if necessary if os_options.get('object_storage_url'): @@ -1414,7 +1422,7 @@ class Connection(object): os_options=None, auth_version="1", cacert=None, insecure=False, cert=None, cert_key=None, ssl_compression=True, retry_on_ratelimit=False, - timeout=None): + timeout=None, session=None): """ :param authurl: authentication URL :param user: user name to authenticate as @@ -1449,7 +1457,9 @@ class Connection(object): this parameter to True will cause a retry after a backoff. :param timeout: The connect timeout for the HTTP connection. + :param session: A keystoneauth session object. """ + self.session = session self.authurl = authurl self.user = user self.key = key @@ -1493,7 +1503,7 @@ class Connection(object): def get_auth(self): self.url, self.token = get_auth(self.authurl, self.user, self.key, - snet=self.snet, + session=self.session, snet=self.snet, auth_version=self.auth_version, os_options=self.os_options, cacert=self.cacert, @@ -1512,8 +1522,8 @@ class Connection(object): None) service_user = opts.get('service_username', None) service_key = opts.get('service_key', None) - return get_auth(self.authurl, service_user, - service_key, + return get_auth(self.authurl, service_user, service_key, + session=self.session, snet=self.snet, auth_version=self.auth_version, os_options=service_options, @@ -1577,10 +1587,15 @@ class Connection(object): logger.exception(err) raise if err.http_status == 401: + if self.session: + should_retry = self.session.invalidate() + else: + # Without a proper session, just check for auth creds + should_retry = all((self.authurl, self.user, self.key)) + self.url = self.token = self.service_token = None - if retried_auth or not all((self.authurl, - self.user, - self.key)): + + if retried_auth or not should_retry: logger.exception(err) raise retried_auth = True diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py index cbb95db..07ae502 100644 --- a/tests/unit/test_swiftclient.py +++ b/tests/unit/test_swiftclient.py @@ -561,6 +561,15 @@ class TestGetAuth(MockHttpTest): self.assertTrue(url.startswith("http")) self.assertTrue(token) + def test_auth_with_session(self): + mock_session = mock.MagicMock() + mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct' + mock_session.get_token.return_value = 'token' + url, token = c.get_auth('http://www.test.com', 'asdf', 'asdf', + session=mock_session) + self.assertEqual(url, 'http://storagehost/v1/acct') + self.assertTrue(token) + class TestGetAccount(MockHttpTest): @@ -1868,6 +1877,39 @@ class TestConnection(MockHttpTest): ('HEAD', '/v1/AUTH_test', '', {'x-auth-token': 'token'}), ]) + def test_session_no_invalidate(self): + mock_session = mock.MagicMock() + mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct' + mock_session.get_token.return_value = 'expired' + mock_session.invalidate.return_value = False + conn = c.Connection(session=mock_session) + fake_conn = self.fake_http_connection(401) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + self.assertRaises(c.ClientException, conn.head_account) + self.assertEqual(mock_session.get_token.mock_calls, [mock.call()]) + self.assertEqual(mock_session.invalidate.mock_calls, [mock.call()]) + + def test_session_can_invalidate(self): + mock_session = mock.MagicMock() + mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct' + mock_session.get_token.side_effect = ['expired', 'token'] + mock_session.invalidate.return_value = True + conn = c.Connection(session=mock_session) + fake_conn = self.fake_http_connection(401, 200) + with mock.patch.multiple('swiftclient.client', + http_connection=fake_conn, + sleep=mock.DEFAULT): + conn.head_account() + self.assertRequests([ + ('HEAD', '/v1/acct', '', {'x-auth-token': 'expired'}), + ('HEAD', '/v1/acct', '', {'x-auth-token': 'token'}), + ]) + self.assertEqual(mock_session.get_token.mock_calls, [ + mock.call(), mock.call()]) + self.assertEqual(mock_session.invalidate.mock_calls, [mock.call()]) + def test_preauth_token_with_no_storage_url_requires_auth(self): conn = c.Connection( 'http://auth.example.com', 'user', 'password', -- cgit v1.2.1