Repository: libcloud Updated Branches: refs/heads/trunk 17c2217b5 -> 022d01edd
Improvements to Google Auth for Storage and Compute and MIME bug fix Signed-off-by: Eric Johnson <erjoh...@google.com> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/022d01ed Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/022d01ed Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/022d01ed Branch: refs/heads/trunk Commit: 022d01edd63bb018dd3054bea3daaff23570f37f Parents: 17c2217 Author: Scott Crunkleton <crunkle...@google.com> Authored: Mon Feb 1 16:03:44 2016 -0800 Committer: Eric Johnson <erjoh...@google.com> Committed: Tue Feb 9 17:12:37 2016 +0000 ---------------------------------------------------------------------- CHANGES.rst | 8 + libcloud/common/google.py | 183 +++++++++++----------- libcloud/compute/drivers/gce.py | 3 +- libcloud/storage/drivers/google_storage.py | 39 +++-- libcloud/test/common/test_google.py | 135 +++++++++------- libcloud/test/storage/test_google_storage.py | 130 ++++----------- 6 files changed, 229 insertions(+), 269 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/022d01ed/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 369d810..0e99f0f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,14 @@ Compute (GITHUB-691) [Jeff Dunham] +Storage +~~~~~~~ + +- Improvements to Google Auth for Storage and Compute and MIME bug fix + (LIBCLOUD-800, GITHUB-689) + [Scott Crunkleton] + + Changes with Apache Libcloud 1.0.0-pre1 --------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/022d01ed/libcloud/common/google.py ---------------------------------------------------------------------- diff --git a/libcloud/common/google.py b/libcloud/common/google.py index 07cf553..7378d8e 100644 --- a/libcloud/common/google.py +++ b/libcloud/common/google.py @@ -609,6 +609,94 @@ class GoogleAuthType(object): return user_id.endswith('.gserviceaccount.com') +class GoogleOAuth2Credential(object): + default_credential_file = '~/.google_libcloud_auth' + + def __init__(self, user_id, key, auth_type=None, credential_file=None, + scopes=None, **kwargs): + self.auth_type = auth_type or GoogleAuthType.guess_type(user_id) + if self.auth_type not in GoogleAuthType.ALL_TYPES: + raise GoogleAuthError('Invalid auth type: %s' % self.auth_type) + if not GoogleAuthType.is_oauth2(self.auth_type): + raise GoogleAuthError(('Auth type %s cannot be used with OAuth2' % + self.auth_type)) + self.user_id = user_id + self.key = key + + default_credential_file = '.'.join([self.default_credential_file, + user_id]) + self.credential_file = credential_file or default_credential_file + # Default scopes to read/write for compute, storage, and dns. + self.scopes = scopes or [ + 'https://www.googleapis.com/auth/compute', + 'https://www.googleapis.com/auth/devstorage.full_control', + 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', + ] + + self.token = self._get_token_from_file() + + if self.auth_type == GoogleAuthType.GCE: + self.oauth2_conn = GoogleGCEServiceAcctAuthConnection( + self.user_id, self.scopes, **kwargs) + elif self.auth_type == GoogleAuthType.SA: + self.oauth2_conn = GoogleServiceAcctAuthConnection( + self.user_id, self.key, self.scopes, **kwargs) + elif self.auth_type == GoogleAuthType.IA: + self.oauth2_conn = GoogleInstalledAppAuthConnection( + self.user_id, self.key, self.scopes, **kwargs) + else: + raise GoogleAuthError('Invalid auth_type: %s' % + str(self.auth_type)) + + if self.token is None: + self.token = self.oauth2_conn.get_new_token() + self._write_token_to_file() + + @property + def access_token(self): + if self.token_expire_utc_datetime < _utcnow(): + self._refresh_token() + return self.token['access_token'] + + @property + def token_expire_utc_datetime(self): + return _from_utc_timestamp(self.token['expire_time']) + + def _refresh_token(self): + self.token = self.oauth2_conn.refresh_token(self.token) + self._write_token_to_file() + + def _get_token_from_file(self): + """ + Read credential file and return token information. + Mocked in libcloud.test.common.google.GoogleTestCase. + + :return: Token information dictionary, or None + :rtype: ``dict`` or ``None`` + """ + token = None + filename = os.path.realpath(os.path.expanduser(self.credential_file)) + + try: + with open(filename, 'r') as f: + data = f.read() + token = json.loads(data) + except IOError: + pass + return token + + def _write_token_to_file(self): + """ + Write token to credential file. + Mocked in libcloud.test.common.google.GoogleTestCase. + """ + filename = os.path.realpath(os.path.expanduser(self.credential_file)) + data = json.dumps(self.token) + with os.fdopen(os.open(filename, os.O_CREAT | os.O_WRONLY, + int('600', 8)), 'w') as f: + f.write(data) + + class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection): """Base connection class for interacting with Google APIs.""" driver = GoogleBaseDriver @@ -616,7 +704,6 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection): host = 'www.googleapis.com' poll_interval = 2.0 timeout = 180 - credential_file = '~/.google_libcloud_auth' def __init__(self, user_id, key=None, auth_type=None, credential_file=None, scopes=None, **kwargs): @@ -648,35 +735,16 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection): read/write access to Compute, Storage, and DNS. :type scopes: ``list`` """ - self.user_id = user_id - self.key = key - if auth_type and auth_type not in GoogleAuthType.ALL_TYPES: - raise GoogleAuthError('Invalid auth type: %s' % auth_type) - self.auth_type = auth_type or GoogleAuthType.guess_type(user_id) - - # OAuth2 stuff and placeholders - self.scopes = scopes - self.oauth2_conn = None - self.oauth2_token = None - if credential_file: - self.credential_file = credential_file - elif self.auth_type == GoogleAuthType.SA: - self.credential_file += '.' + user_id - - if GoogleAuthType.is_oauth2(self.auth_type): - self._init_oauth2(**kwargs) - super(GoogleBaseConnection, self).__init__(user_id, key, **kwargs) + self.oauth2_credential = GoogleOAuth2Credential( + user_id, key, auth_type, credential_file, scopes, **kwargs) + python_ver = '%s.%s.%s' % (sys.version_info[0], sys.version_info[1], sys.version_info[2]) ver_platform = 'Python %s/%s' % (python_ver, sys.platform) self.user_agent_append(ver_platform) - @property - def token_expire_utc_datetime(self): - return _from_utc_timestamp(self.oauth2_token['expire_time']) - def add_default_headers(self, headers): """ @inherits: :class:`Connection.add_default_headers` @@ -692,11 +760,8 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection): @inherits: :class:`Connection.pre_connect_hook` """ - if self.token_expire_utc_datetime < _utcnow(): - self._refresh_oauth2_token() - headers['Authorization'] = 'Bearer %s' % ( - self.oauth2_token['access_token']) - + headers['Authorization'] = ('Bearer ' + + self.oauth2_credential.access_token) return params, headers def encode_data(self, data): @@ -766,65 +831,3 @@ class GoogleBaseConnection(ConnectionUserAndKey, PollingConnection): else: request = self.request_path + action return request - - def _refresh_oauth2_token(self): - self.oauth2_token = self.oauth2_conn.refresh_token(self.oauth2_token) - self._write_token_to_file() - - def _init_oauth2(self, **kwargs): - # Default scopes to read/write for compute, storage, and dns. Can - # override this when calling get_driver() or setting in secrets.py - if not self.scopes: - self.scopes = [ - 'https://www.googleapis.com/auth/compute', - 'https://www.googleapis.com/auth/devstorage.full_control', - 'https://www.googleapis.com/auth/ndev.clouddns.readwrite', - ] - self.oauth2_token = self._get_token_from_file() - - if self.auth_type == GoogleAuthType.GCE: - self.oauth2_conn = GoogleGCEServiceAcctAuthConnection( - self.user_id, self.scopes, **kwargs) - elif self.auth_type == GoogleAuthType.SA: - self.oauth2_conn = GoogleServiceAcctAuthConnection( - self.user_id, self.key, self.scopes, **kwargs) - elif self.auth_type == GoogleAuthType.IA: - self.oauth2_conn = GoogleInstalledAppAuthConnection( - self.user_id, self.key, self.scopes, **kwargs) - else: - raise GoogleAuthError('Invalid auth_type: %s' % - str(self.auth_type)) - - if self.oauth2_token is None: - self.oauth2_token = self.oauth2_conn.get_new_token() - self._write_token_to_file() - - def _get_token_from_file(self): - """ - Read credential file and return token information. - Mocked in libcloud.test.common.google.GoogleTestCase. - - :return: Token information dictionary, or None - :rtype: ``dict`` or ``None`` - """ - token = None - filename = os.path.realpath(os.path.expanduser(self.credential_file)) - - try: - with open(filename, 'r') as f: - data = f.read() - token = json.loads(data) - except IOError: - pass - return token - - def _write_token_to_file(self): - """ - Write token to credential file. - Mocked in libcloud.test.common.google.GoogleTestCase. - """ - filename = os.path.realpath(os.path.expanduser(self.credential_file)) - data = json.dumps(self.oauth2_token) - with os.fdopen(os.open(filename, os.O_CREAT | os.O_WRONLY, - int('600', 8)), 'w') as f: - f.write(data) http://git-wip-us.apache.org/repos/asf/libcloud/blob/022d01ed/libcloud/compute/drivers/gce.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py index 4a5181e..12e296b 100644 --- a/libcloud/compute/drivers/gce.py +++ b/libcloud/compute/drivers/gce.py @@ -22,6 +22,7 @@ import time import sys from libcloud.common.base import LazyObject +from libcloud.common.google import GoogleOAuth2Credential from libcloud.common.google import GoogleResponse from libcloud.common.google import GoogleBaseConnection from libcloud.common.google import GoogleBaseError @@ -1070,7 +1071,7 @@ class GCENodeDriver(NodeDriver): self.project = project self.scopes = scopes self.credential_file = credential_file or \ - GCEConnection.credential_file + '.' + self.project + GoogleOAuth2Credential.default_credential_file + '.' + self.project super(GCENodeDriver, self).__init__(user_id, key, **kwargs) http://git-wip-us.apache.org/repos/asf/libcloud/blob/022d01ed/libcloud/storage/drivers/google_storage.py ---------------------------------------------------------------------- diff --git a/libcloud/storage/drivers/google_storage.py b/libcloud/storage/drivers/google_storage.py index ca80c97..580a29c 100644 --- a/libcloud/storage/drivers/google_storage.py +++ b/libcloud/storage/drivers/google_storage.py @@ -17,8 +17,9 @@ import copy import email.utils +from libcloud.common.base import ConnectionUserAndKey from libcloud.common.google import GoogleAuthType -from libcloud.common.google import GoogleBaseConnection +from libcloud.common.google import GoogleOAuth2Credential from libcloud.storage.drivers.s3 import BaseS3Connection from libcloud.storage.drivers.s3 import BaseS3StorageDriver from libcloud.storage.drivers.s3 import S3RawResponse @@ -31,12 +32,12 @@ API_VERSION = '2006-03-01' NAMESPACE = 'http://doc.s3.amazonaws.com/%s' % (API_VERSION) -class GoogleStorageConnection(GoogleBaseConnection): +class GoogleStorageConnection(ConnectionUserAndKey): """ Represents a single connection to the Google storage API endpoint. This can either authenticate via the Google OAuth2 methods or via - the S3 interoperability method. + the S3 HMAC interoperability method. """ host = 'storage.googleapis.com' @@ -46,27 +47,23 @@ class GoogleStorageConnection(GoogleBaseConnection): def __init__(self, user_id, key, secure, auth_type=None, credential_file=None, **kwargs): - super(GoogleStorageConnection, self).__init__( - user_id, key, secure=secure, auth_type=auth_type, - credential_file=credential_file, **kwargs) + self.auth_type = auth_type or GoogleAuthType.guess_type(user_id) + if GoogleAuthType.is_oauth2(self.auth_type): + self.oauth2_credential = GoogleOAuth2Credential( + user_id, key, self.auth_type, credential_file, **kwargs) + else: + self.oauth2_credential = None + super(GoogleStorageConnection, self).__init__(user_id, key, secure, + **kwargs) def add_default_headers(self, headers): - if self.auth_type == GoogleAuthType.GCS_S3: - date = email.utils.formatdate(usegmt=True) - headers['Date'] = date - else: - headers = super(GoogleStorageConnection, - self).add_default_headers(headers) + date = email.utils.formatdate(usegmt=True) + headers['Date'] = date project = self.get_project() if project: headers[self.PROJECT_ID_HEADER] = project return headers - def encode_data(self, data): - if self.auth_type == GoogleAuthType.GCS_S3: - return data - return super(GoogleStorageConnection, self).encode_data(data) - def get_project(self): return getattr(self.driver, 'project') @@ -76,8 +73,8 @@ class GoogleStorageConnection(GoogleBaseConnection): headers['Authorization'] = '%s %s:%s' % (SIGNATURE_IDENTIFIER, self.user_id, signature) else: - params, headers = super(GoogleStorageConnection, - self).pre_connect_hook(params, headers) + headers['Authorization'] = ('Bearer ' + + self.oauth2_credential.access_token) return params, headers def _get_s3_auth_signature(self, params, headers): @@ -126,8 +123,8 @@ class GoogleStorageDriver(BaseS3StorageDriver): driver = GoogleStorageDriver(key=foo , secret=bar, ...) - Can also authenticate via Google Cloud Storage's S3 interoperability API. - S3 user keys are 20 alphanumeric characters, starting with GOOG. + Can also authenticate via Google Cloud Storage's S3 HMAC interoperability + API. S3 user keys are 20 alphanumeric characters, starting with GOOG. Example:: http://git-wip-us.apache.org/repos/asf/libcloud/blob/022d01ed/libcloud/test/common/test_google.py ---------------------------------------------------------------------- diff --git a/libcloud/test/common/test_google.py b/libcloud/test/common/test_google.py index d463147..2266ddd 100644 --- a/libcloud/test/common/test_google.py +++ b/libcloud/test/common/test_google.py @@ -32,6 +32,7 @@ from libcloud.common.google import (GoogleAuthError, GoogleInstalledAppAuthConnection, GoogleServiceAcctAuthConnection, GoogleGCEServiceAcctAuthConnection, + GoogleOAuth2Credential, GoogleBaseConnection, _utcnow, _utc_timestamp) @@ -47,8 +48,8 @@ except ImportError: SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) -PEM_KEY = os.path.join(SCRIPT_PATH, "fixtures", "google", "pkey.pem") -JSON_KEY = os.path.join(SCRIPT_PATH, "fixtures", "google", "pkey.json") +PEM_KEY = os.path.join(SCRIPT_PATH, 'fixtures', 'google', 'pkey.pem') +JSON_KEY = os.path.join(SCRIPT_PATH, 'fixtures', 'google', 'pkey.json') with open(JSON_KEY, 'r') as f: KEY_STR = json.loads(f.read())['private_key'] @@ -120,12 +121,12 @@ class GoogleTestCase(LibcloudTestCase): 'libcloud.common.google.GoogleAuthType._is_gce', return_value=False) _read_token_file_patcher = mock.patch( - 'libcloud.common.google.GoogleBaseConnection._get_token_from_file', + 'libcloud.common.google.GoogleOAuth2Credential._get_token_from_file', return_value=STUB_TOKEN_FROM_FILE ) _write_token_file_patcher = mock.patch( - 'libcloud.common.google.GoogleBaseConnection._write_token_to_file') + 'libcloud.common.google.GoogleOAuth2Credential._write_token_to_file') _ia_get_code_patcher = mock.patch( 'libcloud.common.google.GoogleInstalledAppAuthConnection.get_code', @@ -222,67 +223,103 @@ class GoogleInstalledAppAuthConnectionTest(GoogleTestCase): class GoogleAuthTypeTest(GoogleTestCase): def test_guess(self): - self.assertEqual( - GoogleAuthType.guess_type(GCE_PARAMS[0]), - GoogleAuthType.SA) - self.assertEqual( - GoogleAuthType.guess_type(GCE_PARAMS_IA[0]), - GoogleAuthType.IA) + self.assertEqual(GoogleAuthType.guess_type(GCE_PARAMS[0]), + GoogleAuthType.SA) + self.assertEqual(GoogleAuthType.guess_type(GCE_PARAMS_IA[0]), + GoogleAuthType.IA) with mock.patch.object(GoogleAuthType, '_is_gce', return_value=True): - self.assertEqual( - GoogleAuthType.guess_type(GCE_PARAMS_GCE[0]), - GoogleAuthType.GCE) + self.assertEqual(GoogleAuthType.guess_type(GCE_PARAMS_GCE[0]), + GoogleAuthType.GCE) self.assertEqual( GoogleAuthType.guess_type(GCS_S3_PARAMS[0]), GoogleAuthType.GCS_S3) -class GoogleBaseConnectionTest(GoogleTestCase): - """ - Tests for GoogleBaseConnection - """ +class GoogleOAuth2CredentialTest(GoogleTestCase): - def setUp(self): - GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp, - GoogleAuthMockHttp) - self.mock_scopes = ['https://www.googleapis.com/auth/foo'] - kwargs = {'scopes': self.mock_scopes, - 'auth_type': GoogleAuthType.IA} - self.conn = GoogleBaseConnection(*GCE_PARAMS, **kwargs) + def test_init_oauth2(self): + kwargs = {'auth_type': GoogleAuthType.IA} + cred = GoogleOAuth2Credential(*GCE_PARAMS, **kwargs) + + # If there is a viable token file, this gets used first + self.assertEqual(cred.token, STUB_TOKEN_FROM_FILE) - def test_auth_type(self): - self.assertRaises(GoogleAuthError, GoogleBaseConnection, *GCE_PARAMS, + # No token file, get a new token. Check that it gets written to file. + with mock.patch.object(GoogleOAuth2Credential, '_get_token_from_file', + return_value=None): + cred = GoogleOAuth2Credential(*GCE_PARAMS, **kwargs) + expected = STUB_IA_TOKEN + expected['expire_time'] = cred.token['expire_time'] + self.assertEqual(cred.token, expected) + cred._write_token_to_file.assert_called_once_with() + + def test_refresh(self): + args = list(GCE_PARAMS) + [GoogleAuthType.GCE] + cred = GoogleOAuth2Credential(*args) + cred._refresh_token = mock.Mock() + + # Test getting an unexpired access token. + tomorrow = datetime.datetime.now() + datetime.timedelta(days=1) + cred.token = {'access_token': 'Access Token!', + 'expire_time': _utc_timestamp(tomorrow)} + cred.access_token + self.assertFalse(cred._refresh_token.called) + + # Test getting an expired access token. + yesterday = datetime.datetime.now() - datetime.timedelta(days=1) + cred.token = {'access_token': 'Access Token!', + 'expire_time': _utc_timestamp(yesterday)} + cred.access_token + self.assertTrue(cred._refresh_token.called) + + def test_auth_connection(self): + # Test a bogus auth type + self.assertRaises(GoogleAuthError, GoogleOAuth2Credential, *GCE_PARAMS, **{'auth_type': 'XX'}) + # Try to create an OAuth2 credential when dealing with a GCS S3 + # interoperability auth type. + self.assertRaises(GoogleAuthError, GoogleOAuth2Credential, *GCE_PARAMS, + **{'auth_type': GoogleAuthType.GCS_S3}) - kwargs = {'scopes': self.mock_scopes} + kwargs = {} if SHA256: kwargs['auth_type'] = GoogleAuthType.SA - conn1 = GoogleBaseConnection(*GCE_PARAMS_PEM_KEY, **kwargs) - self.assertTrue(isinstance(conn1.oauth2_conn, + cred1 = GoogleOAuth2Credential(*GCE_PARAMS_PEM_KEY, **kwargs) + self.assertTrue(isinstance(cred1.oauth2_conn, GoogleServiceAcctAuthConnection)) - conn1 = GoogleBaseConnection(*GCE_PARAMS_JSON_KEY, **kwargs) - self.assertTrue(isinstance(conn1.oauth2_conn, + cred1 = GoogleOAuth2Credential(*GCE_PARAMS_JSON_KEY, **kwargs) + self.assertTrue(isinstance(cred1.oauth2_conn, GoogleServiceAcctAuthConnection)) - conn1 = GoogleBaseConnection(*GCE_PARAMS_KEY, **kwargs) - self.assertTrue(isinstance(conn1.oauth2_conn, + cred1 = GoogleOAuth2Credential(*GCE_PARAMS_KEY, **kwargs) + self.assertTrue(isinstance(cred1.oauth2_conn, GoogleServiceAcctAuthConnection)) kwargs['auth_type'] = GoogleAuthType.IA - conn2 = GoogleBaseConnection(*GCE_PARAMS_IA, **kwargs) - self.assertTrue(isinstance(conn2.oauth2_conn, + cred2 = GoogleOAuth2Credential(*GCE_PARAMS_IA, **kwargs) + self.assertTrue(isinstance(cred2.oauth2_conn, GoogleInstalledAppAuthConnection)) kwargs['auth_type'] = GoogleAuthType.GCE - conn3 = GoogleBaseConnection(*GCE_PARAMS_GCE, **kwargs) - self.assertTrue(isinstance(conn3.oauth2_conn, + cred3 = GoogleOAuth2Credential(*GCE_PARAMS_GCE, **kwargs) + self.assertTrue(isinstance(cred3.oauth2_conn, GoogleGCEServiceAcctAuthConnection)) - kwargs['auth_type'] = GoogleAuthType.GCS_S3 - conn4 = GoogleBaseConnection(*GCS_S3_PARAMS, **kwargs) - self.assertIsNone(conn4.oauth2_conn) + +class GoogleBaseConnectionTest(GoogleTestCase): + """ + Tests for GoogleBaseConnection + """ + + def setUp(self): + GoogleBaseAuthConnection.conn_classes = (GoogleAuthMockHttp, + GoogleAuthMockHttp) + self.mock_scopes = ['https://www.googleapis.com/auth/foo'] + kwargs = {'scopes': self.mock_scopes, + 'auth_type': GoogleAuthType.IA} + self.conn = GoogleBaseConnection(*GCE_PARAMS, **kwargs) def test_add_default_headers(self): old_headers = {} @@ -347,24 +384,6 @@ class GoogleBaseConnectionTest(GoogleTestCase): self.assertEqual(request1, expected_request) self.assertEqual(request2, expected_request) - def test_init_oauth2(self): - mock_scopes = ['https://www.googleapis.com/auth/foo'] - kwargs = {'scopes': mock_scopes, - 'auth_type': GoogleAuthType.IA} - conn = GoogleBaseConnection(*GCE_PARAMS, **kwargs) - - # If there is a viable token file, this gets used first - self.assertEqual(conn.oauth2_token, STUB_TOKEN_FROM_FILE) - - # No token file, get a new token. Check that it gets written to file. - with mock.patch.object(GoogleBaseConnection, - '_get_token_from_file', return_value=None): - conn = GoogleBaseConnection(*GCE_PARAMS, **kwargs) - expected = STUB_IA_TOKEN - expected['expire_time'] = conn.oauth2_token['expire_time'] - self.assertEqual(conn.oauth2_token, expected) - conn._write_token_to_file.assert_called_once_with() - class GoogleAuthMockHttp(MockHttp): """ http://git-wip-us.apache.org/repos/asf/libcloud/blob/022d01ed/libcloud/test/storage/test_google_storage.py ---------------------------------------------------------------------- diff --git a/libcloud/test/storage/test_google_storage.py b/libcloud/test/storage/test_google_storage.py index 001a9f3..2668c50 100644 --- a/libcloud/test/storage/test_google_storage.py +++ b/libcloud/test/storage/test_google_storage.py @@ -60,41 +60,12 @@ class GoogleStorageMockHttp(S3MockHttp): class GoogleStorageConnectionTest(GoogleTestCase): @mock.patch('email.utils.formatdate') - @mock.patch('libcloud.common.google.' - 'GoogleBaseConnection.add_default_headers') - def test_add_default_headers(self, mock_base_method, mock_formatdate): + def test_add_default_headers(self, mock_formatdate): mock_formatdate.return_value = TODAY starting_headers = {'starting': 'headers'} - changed_headers = {'changed': 'headers'} project = 'foo-project' - # Should use base add_default_headers - mock_base_method.return_value = dict(changed_headers) - conn = CONN_CLS('foo_user', 'bar_key', secure=True, - auth_type=GoogleAuthType.GCE) - conn.get_project = lambda: None - self.assertEqual( - conn.add_default_headers(dict(starting_headers)), - dict(changed_headers) - ) - mock_base_method.assert_called_once_with(dict(starting_headers)) - mock_base_method.reset_mock() - - # Base add_default_headers with project - mock_base_method.return_value = dict(changed_headers) - conn = CONN_CLS('foo_user', 'bar_key', secure=True, - auth_type=GoogleAuthType.GCE) - conn.get_project = lambda: project - headers = dict(changed_headers) - headers[CONN_CLS.PROJECT_ID_HEADER] = project - self.assertEqual( - conn.add_default_headers(dict(starting_headers)), - headers - ) - mock_base_method.assert_called_once_with(dict(starting_headers)) - mock_base_method.reset_mock() - - # Should use S3 add_default_headers + # Modify headers when there is no project. conn = CONN_CLS('foo_user', 'bar_key', secure=True, auth_type=GoogleAuthType.GCS_S3) conn.get_project = lambda: None @@ -102,9 +73,8 @@ class GoogleStorageConnectionTest(GoogleTestCase): headers['Date'] = TODAY self.assertEqual(conn.add_default_headers(dict(starting_headers)), headers) - mock_base_method.assert_not_called() - # S3 add_default_headers with project + # Modify headers when there is a project. conn = CONN_CLS('foo_user', 'bar_key', secure=True, auth_type=GoogleAuthType.GCS_S3) conn.get_project = lambda: project @@ -113,44 +83,25 @@ class GoogleStorageConnectionTest(GoogleTestCase): headers[CONN_CLS.PROJECT_ID_HEADER] = project self.assertEqual(conn.add_default_headers(dict(starting_headers)), headers) - mock_base_method.assert_not_called() - - @mock.patch('libcloud.common.google.GoogleBaseConnection.encode_data') - def test_encode_data(self, mock_base_method): - old_data = 'old data!' - new_data = 'new data!' - - # Should use Base encode_data - mock_base_method.return_value = new_data - conn = CONN_CLS('foo_user', 'bar_key', secure=True, - auth_type=GoogleAuthType.GCE) - self.assertEqual(conn.encode_data(old_data), new_data) - mock_base_method.assert_called_once_with(old_data) - mock_base_method.reset_mock() - - # Should use S3 encode_data (which does nothing) - conn = CONN_CLS('foo_user', 'bar_key', secure=True, - auth_type=GoogleAuthType.GCS_S3) - self.assertEqual(conn.encode_data(old_data), old_data) - mock_base_method.assert_not_called() @mock.patch('libcloud.storage.drivers.s3.' 'BaseS3Connection.get_auth_signature') def test_get_s3_auth_signature(self, mock_s3_auth_sig_method): # Check that the S3 HMAC signature method is used. - # Check that headers are copied and modified before calling the method. + # Check that headers are copied and modified properly before calling + # the signature method. mock_s3_auth_sig_method.return_value = 'mock signature!' starting_params = {} starting_headers = { 'Date': TODAY, - 'x-goog-foo': 'MAINTAIN UPPERCASE!', - 'x-Goog-bar': 'Header should be lowered', + 'x-goog-foo': 'X-GOOG: MAINTAIN UPPERCASE!', + 'x-Goog-bar': 'Header key should be lowered', 'Other': 'LOWER THIS!' } modified_headers = { 'date': TODAY, - 'x-goog-foo': 'MAINTAIN UPPERCASE!', - 'x-goog-bar': 'Header should be lowered', + 'x-goog-foo': 'X-GOOG: MAINTAIN UPPERCASE!', + 'x-goog-bar': 'Header key should be lowered', 'other': 'lower this!' } @@ -171,38 +122,30 @@ class GoogleStorageConnectionTest(GoogleTestCase): vendor_prefix='x-goog' ) - @mock.patch('libcloud.common.google.GoogleBaseConnection.pre_connect_hook') - def test_pre_connect_hook_oauth2(self, mock_base_hook): - # Should use BaseGoogleConnection pre_connect_hook - # Check that the base hook is called. + @mock.patch('libcloud.common.google.GoogleOAuth2Credential') + def test_pre_connect_hook_oauth2(self, mock_oauth2_credential_init): + # Check that we get the Authorization header from the OAuth2 token, + # not from the HMAC signature method. + # Check that the headers and pa + mock_oauth2_credential_init.return_value = mock.Mock() starting_params = {'starting': 'params'} - changed_params = {'changed': 'params'} starting_headers = {'starting': 'headers'} - changed_headers = {'changed': 'headers'} - mock_base_hook.return_value = (dict(changed_params), - dict(changed_headers)) conn = CONN_CLS('foo_user', 'bar_key', secure=True, auth_type=GoogleAuthType.GCE) - result = conn.pre_connect_hook( - dict(starting_params), - dict(starting_headers) - ) - self.assertEqual( - result, - (dict(changed_params), dict(changed_headers)) - ) - mock_base_hook.assert_called_once_with( - dict(starting_params), - dict(starting_headers) - ) - mock_base_hook.reset_mock() + conn._get_s3_auth_signature = mock.Mock() + conn.oauth2_credential = mock.Mock() + conn.oauth2_credential.access_token = 'Access_Token!' + expected_headers = dict(starting_headers) + expected_headers['Authorization'] = 'Bearer Access_Token!' + result = conn.pre_connect_hook(dict(starting_params), + dict(starting_headers)) + self.assertEqual(result, (starting_params, expected_headers)) - @mock.patch('libcloud.common.google.GoogleBaseConnection.pre_connect_hook') - def test_pre_connect_hook_hmac(self, mock_base_hook): + def test_pre_connect_hook_hmac(self): # Check that we call for a HMAC signature, passing params and headers # Check that we properly apply the HMAC signature. - # Check that we don't use the BaseGoogleConnection pre_connect_hook. + # Check that we don't use OAuth2 credentials. starting_params = {'starting': 'params'} starting_headers = {'starting': 'headers'} @@ -222,23 +165,12 @@ class GoogleStorageConnectionTest(GoogleTestCase): '%s %s:%s' % (google_storage.SIGNATURE_IDENTIFIER, 'foo_user', 'fake signature!') ) - result = conn.pre_connect_hook( - dict(starting_params), - dict(starting_headers) - ) - self.assertEqual( - result, - (dict(starting_params), expected_headers) - ) - mock_base_hook.assert_not_called() - self.assertEqual( - fake_hmac_method.params_passed, - starting_params - ) - self.assertEqual( - fake_hmac_method.headers_passed, - starting_headers - ) + result = conn.pre_connect_hook(dict(starting_params), + dict(starting_headers)) + self.assertEqual(result, (dict(starting_params), expected_headers)) + self.assertEqual(fake_hmac_method.params_passed, starting_params) + self.assertEqual(fake_hmac_method.headers_passed, starting_headers) + self.assertIsNone(conn.oauth2_credential) class GoogleStorageTests(S3Tests, GoogleTestCase):