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

Stop sending scopes in token requests on GCE #419

Merged
merged 1 commit into from
Feb 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
26 changes: 19 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,19 @@ class AppAssertionCredentials(AssertionCredentials):
"""

@util.positional(2)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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.

This comment was marked as spam.

self.scope = util.scopes_to_string(scope)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

self.kwargs = kwargs

Expand All @@ -83,10 +97,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 +119,7 @@ def serialization_data(self):
'Cannot serialize credentials for GCE service accounts.')

def create_scoped_required(self):
return not self.scope
return False

This comment was marked as spam.


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