diff --git a/oauth2client/client.py b/oauth2client/client.py index 4b246c5..fd0d699 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1551,7 +1551,7 @@ def _get_application_default_credential_GAE(): def _get_application_default_credential_GCE(): from oauth2client.contrib.gce import AppAssertionCredentials - return AppAssertionCredentials([]) + return AppAssertionCredentials() class AssertionCredentials(GoogleCredentials): diff --git a/oauth2client/contrib/gce.py b/oauth2client/contrib/gce.py index b3fd92b..53a7b1c 100644 --- a/oauth2client/contrib/gce.py +++ b/oauth2client/contrib/gce.py @@ -19,6 +19,8 @@ Utilities for making it easier to use OAuth 2.0 on Google Compute Engine. import json import logging +import warnings + from six.moves import http_client from six.moves import urllib @@ -34,7 +36,13 @@ logger = logging.getLogger(__name__) # URI Template for the endpoint that returns access_tokens. META = ('http://metadata.google.internal/computeMetadata/v1/instance/' - 'service-accounts/default/token{?scope}') + 'service-accounts/default/token') +_SCOPES_WARNING = """\ +You have requested explicit scopes to be used with a GCE service account. +Using this argument will have no effect on the actual scopes for tokens +requested. These scopes are set at VM instance creation time and +can't be overridden in the request. +""" class AppAssertionCredentials(AssertionCredentials): @@ -51,13 +59,19 @@ class AppAssertionCredentials(AssertionCredentials): """ @util.positional(2) - def __init__(self, scope, **kwargs): + def __init__(self, scope='', **kwargs): """Constructor for AppAssertionCredentials Args: scope: string or iterable of strings, scope(s) of the credentials - being requested. + being requested. Using this argument will have no effect on + the actual scopes for tokens requested. These scopes are + set at VM instance creation time and won't change. """ + if scope: + warnings.warn(_SCOPES_WARNING) + # This is just provided for backwards compatibility, but is not + # used by this class. self.scope = util.scopes_to_string(scope) self.kwargs = kwargs @@ -83,10 +97,8 @@ class AppAssertionCredentials(AssertionCredentials): Raises: HttpAccessTokenRefreshError: When the refresh fails. """ - query = '?scope=%s' % urllib.parse.quote(self.scope, '') - uri = META.replace('{?scope}', query) response, content = http_request( - uri, headers={'Metadata-Flavor': 'Google'}) + META, headers={'Metadata-Flavor': 'Google'}) content = _from_bytes(content) if response.status == http_client.OK: try: @@ -107,7 +119,7 @@ class AppAssertionCredentials(AssertionCredentials): 'Cannot serialize credentials for GCE service accounts.') def create_scoped_required(self): - return not self.scope + return False def create_scoped(self, scopes): return AppAssertionCredentials(scopes, **self.kwargs) diff --git a/tests/contrib/test_gce.py b/tests/contrib/test_gce.py index b61576c..3c8f33c 100644 --- a/tests/contrib/test_gce.py +++ b/tests/contrib/test_gce.py @@ -25,6 +25,7 @@ from oauth2client._helpers import _to_bytes from oauth2client.client import AccessTokenRefreshError from oauth2client.client import Credentials from oauth2client.client import save_to_well_known_file +from oauth2client.contrib.gce import _SCOPES_WARNING from oauth2client.contrib.gce import AppAssertionCredentials @@ -34,16 +35,23 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' class AppAssertionCredentialsTests(unittest.TestCase): def test_constructor(self): + credentials = AppAssertionCredentials(foo='bar') + self.assertEqual(credentials.scope, '') + self.assertEqual(credentials.kwargs, {'foo': 'bar'}) + self.assertEqual(credentials.assertion_type, None) + + @mock.patch('warnings.warn') + def test_constructor_with_scopes(self, warn_mock): scope = 'http://example.com/a http://example.com/b' scopes = scope.split() credentials = AppAssertionCredentials(scope=scopes, foo='bar') self.assertEqual(credentials.scope, scope) self.assertEqual(credentials.kwargs, {'foo': 'bar'}) self.assertEqual(credentials.assertion_type, None) + warn_mock.assert_called_once_with(_SCOPES_WARNING) def test_to_json_and_from_json(self): - credentials = AppAssertionCredentials( - scope=['http://example.com/a', 'http://example.com/b']) + credentials = AppAssertionCredentials() json = credentials.to_json() credentials_from_json = Credentials.new_from_json(json) self.assertEqual(credentials.access_token, @@ -58,8 +66,7 @@ class AppAssertionCredentialsTests(unittest.TestCase): http.request = mock.MagicMock( return_value=(mock.Mock(status=http_client.OK), return_val)) - scopes = ['http://example.com/a', 'http://example.com/b'] - credentials = AppAssertionCredentials(scope=scopes) + credentials = AppAssertionCredentials() self.assertEquals(None, credentials.access_token) credentials.refresh(http) self.assertEquals(access_token, credentials.access_token) @@ -67,10 +74,8 @@ class AppAssertionCredentialsTests(unittest.TestCase): base_metadata_uri = ( 'http://metadata.google.internal/computeMetadata/v1/instance/' 'service-accounts/default/token') - escaped_scopes = urllib.parse.quote(' '.join(scopes), safe='') - request_uri = base_metadata_uri + '?scope=' + escaped_scopes http.request.assert_called_once_with( - request_uri, headers={'Metadata-Flavor': 'Google'}) + base_metadata_uri, headers={'Metadata-Flavor': 'Google'}) def test_refresh_success(self): self._refresh_success_helper(bytes_response=False) @@ -84,8 +89,7 @@ class AppAssertionCredentialsTests(unittest.TestCase): http.request = mock.MagicMock( return_value=(mock.Mock(status=http_client.OK), content)) - credentials = AppAssertionCredentials( - scope=['http://example.com/a', 'http://example.com/b']) + credentials = AppAssertionCredentials() self.assertRaises(AccessTokenRefreshError, credentials.refresh, http) def test_refresh_failure_400(self): @@ -94,9 +98,7 @@ class AppAssertionCredentialsTests(unittest.TestCase): http.request = mock.MagicMock( return_value=(mock.Mock(status=http_client.BAD_REQUEST), content)) - credentials = AppAssertionCredentials( - scope=['http://example.com/a', 'http://example.com/b']) - + credentials = AppAssertionCredentials() exception_caught = None try: credentials.refresh(http) @@ -112,9 +114,7 @@ class AppAssertionCredentialsTests(unittest.TestCase): http.request = mock.MagicMock( return_value=(mock.Mock(status=http_client.NOT_FOUND), content)) - credentials = AppAssertionCredentials( - scope=['http://example.com/a', 'http://example.com/b']) - + credentials = AppAssertionCredentials() exception_caught = None try: credentials.refresh(http) @@ -127,24 +127,28 @@ class AppAssertionCredentialsTests(unittest.TestCase): self.assertEqual(str(exception_caught), expanded_content) def test_serialization_data(self): - credentials = AppAssertionCredentials(scope=[]) + credentials = AppAssertionCredentials() self.assertRaises(NotImplementedError, getattr, credentials, 'serialization_data') def test_create_scoped_required_without_scopes(self): - credentials = AppAssertionCredentials([]) - self.assertTrue(credentials.create_scoped_required()) - - def test_create_scoped_required_with_scopes(self): - credentials = AppAssertionCredentials(['dummy_scope']) + credentials = AppAssertionCredentials() self.assertFalse(credentials.create_scoped_required()) - def test_create_scoped(self): - credentials = AppAssertionCredentials([]) + @mock.patch('warnings.warn') + def test_create_scoped_required_with_scopes(self, warn_mock): + credentials = AppAssertionCredentials(['dummy_scope']) + self.assertFalse(credentials.create_scoped_required()) + warn_mock.assert_called_once_with(_SCOPES_WARNING) + + @mock.patch('warnings.warn') + def test_create_scoped(self, warn_mock): + credentials = AppAssertionCredentials() new_credentials = credentials.create_scoped(['dummy_scope']) self.assertNotEqual(credentials, new_credentials) self.assertTrue(isinstance(new_credentials, AppAssertionCredentials)) self.assertEqual('dummy_scope', new_credentials.scope) + warn_mock.assert_called_once_with(_SCOPES_WARNING) def test_get_access_token(self): http = mock.MagicMock() @@ -152,14 +156,14 @@ class AppAssertionCredentialsTests(unittest.TestCase): return_value=(mock.Mock(status=http_client.OK), '{"access_token": "this-is-a-token"}')) - credentials = AppAssertionCredentials(['dummy_scope']) + credentials = AppAssertionCredentials() token = credentials.get_access_token(http=http) self.assertEqual('this-is-a-token', token.access_token) self.assertEqual(None, token.expires_in) http.request.assert_called_once_with( 'http://metadata.google.internal/computeMetadata/v1/instance/' - 'service-accounts/default/token?scope=dummy_scope', + 'service-accounts/default/token', headers={'Metadata-Flavor': 'Google'}) def test_save_to_well_known_file(self): @@ -167,7 +171,7 @@ class AppAssertionCredentialsTests(unittest.TestCase): ORIGINAL_ISDIR = os.path.isdir try: os.path.isdir = lambda path: True - credentials = AppAssertionCredentials([]) + credentials = AppAssertionCredentials() self.assertRaises(NotImplementedError, save_to_well_known_file, credentials) finally: