Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Commit

Permalink
Stop sending scopes in token requests on GCE
Browse files Browse the repository at this point in the history
Also check to warn a user when a scope is passed (since scopes
have no effect for GCE service accounts).
  • Loading branch information
dhermes committed Feb 19, 2016
1 parent 0c79960 commit 01c894e
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 32 deletions.
2 changes: 1 addition & 1 deletion oauth2client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
24 changes: 17 additions & 7 deletions oauth2client/contrib/gce.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import json
import logging
import warnings

from six.moves import http_client
from six.moves import urllib

Expand All @@ -34,7 +36,13 @@

# 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):
Expand All @@ -51,13 +59,17 @@ 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)
self.scope = util.scopes_to_string(scope)
self.kwargs = kwargs

Expand All @@ -83,10 +95,8 @@ def _refresh(self, http_request):
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:
Expand All @@ -107,7 +117,7 @@ def serialization_data(self):
'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)
52 changes: 28 additions & 24 deletions tests/contrib/test_gce.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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


Expand All @@ -34,16 +35,23 @@
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,
Expand All @@ -58,19 +66,16 @@ def _refresh_success_helper(self, bytes_response=False):
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)

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)
Expand All @@ -84,8 +89,7 @@ def test_refresh_failure_bad_json(self):
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):
Expand All @@ -94,9 +98,7 @@ def test_refresh_failure_400(self):
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)
Expand All @@ -112,9 +114,7 @@ def test_refresh_failure_404(self):
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)
Expand All @@ -127,47 +127,51 @@ def test_refresh_failure_404(self):
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())
credentials = AppAssertionCredentials()
self.assertFalse(credentials.create_scoped_required())

def test_create_scoped_required_with_scopes(self):
@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)

def test_create_scoped(self):
credentials = AppAssertionCredentials([])
@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()
http.request = mock.MagicMock(
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):
import os
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:
Expand Down

0 comments on commit 01c894e

Please sign in to comment.