Skip to content
This repository has been archived by the owner on Jan 18, 2025. It is now read-only.

Update GCE AppAssertionCredentials #476

Closed
wants to merge 39 commits into from
Closed
Changes from 3 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b24a4e3
Updated gce.AppAssertionCredentials
elibixby Mar 22, 2016
e4407d2
Typo
elibixby Mar 23, 2016
bcc1198
Move back to JIT field population
elibixby Mar 25, 2016
b721ec8
Move back to 0.1 endpoint
elibixby Mar 25, 2016
73b0a8d
no project/ prefix in 0.1 endpoint
elibixby Mar 25, 2016
96e7250
Moved back to v1 endpoint
elibixby Mar 25, 2016
c0b2300
More specific exception
elibixby Mar 25, 2016
b593ac0
Include scopes and aliases in serialization
elibixby Mar 28, 2016
ae03714
Added tests to green, ready for review
elibixby Mar 29, 2016
eed5f04
python 2.6 fix -_-
elibixby Mar 29, 2016
3f70a0e
Check scopes and error
elibixby Mar 29, 2016
d2f1bbb
Fixed silliness + coverage
elibixby Mar 29, 2016
0737f22
More docstrings
elibixby Mar 29, 2016
81636e6
AttributeError -> ValueError
elibixby Mar 29, 2016
42cc338
Broken doc string
elibixby Mar 29, 2016
099181c
AttributeError -> ValueError in tests
elibixby Mar 29, 2016
1c42eaf
Bad endpoint for metadata server
elibixby Mar 30, 2016
f1a24bf
Still bad metadata endpoint >_<
elibixby Mar 30, 2016
2c7ec57
Cannot use recursive with project-id
elibixby Mar 30, 2016
3406f9c
token endpoint: no recursive but json ='(
elibixby Mar 30, 2016
21e15da
Fix access token call
elibixby Mar 30, 2016
e535203
Revert "Fix access token call"
elibixby Mar 30, 2016
0ee4fa7
Revert "token endpoint: no recursive but json ='("
elibixby Mar 30, 2016
9d582fd
Use content-type to decode
elibixby Mar 30, 2016
5865356
Added test for raw content
elibixby Mar 30, 2016
48e1e9d
Clean up
elibixby Mar 30, 2016
e6e258c
style
elibixby Mar 30, 2016
82092f0
More style fixes
elibixby Mar 30, 2016
b07735b
Doc string fixes
elibixby Mar 30, 2016
a9f2d74
Fix import order
elibixby Mar 31, 2016
55809b0
Alphabetical order for imports
elibixby Mar 31, 2016
15441b9
Accidental import from future commit
elibixby Mar 31, 2016
baada2a
Style + docstring fixes
elibixby Apr 3, 2016
1ffa704
Style fixes
elibixby Apr 4, 2016
ccad050
Docstring and error prose fixes
elibixby Apr 4, 2016
35dc570
Style fix
elibixby Apr 5, 2016
d4a1bfc
More style fixes
elibixby Apr 5, 2016
7afce10
Typo
elibixby Apr 5, 2016
6bb58a3
project id isn't really a property of creds
elibixby Apr 7, 2016
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
159 changes: 82 additions & 77 deletions oauth2client/contrib/gce.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

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

from oauth2client._helpers import _from_bytes
from oauth2client import util
Expand All @@ -36,10 +35,7 @@
logger = logging.getLogger(__name__)

# URI Template for the endpoint that returns access_tokens.
_METADATA_ROOT = ('http://metadata.google.internal/computeMetadata/v1/'
'instance/service-accounts/default/')
META = _METADATA_ROOT + 'token'
_DEFAULT_EMAIL_METADATA = _METADATA_ROOT + 'email'
_METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1'
_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

This comment was marked as spam.

Expand All @@ -48,28 +44,34 @@
"""


def _get_service_account_email(http_request=None):
"""Get the GCE service account email from the current environment.

Args:
http_request: callable, (Optional) a callable that matches the method
signature of httplib2.Http.request, used to make
the request to the metadata service.

Returns:
tuple, A pair where the first entry is an optional response (from a
failed request) and the second is service account email found (as
a string).
"""
if http_request is None:
def _get_metadata(http_request=None, *path):

This comment was marked as spam.

if not http_request:

This comment was marked as spam.

http_request = httplib2.Http().request
full_path = "/".join(path.insert(0, _METADATA_ROOT))

This comment was marked as spam.

response, content = http_request(
_DEFAULT_EMAIL_METADATA, headers={'Metadata-Flavor': 'Google'})
full_path,
headers={'Metadata-Flavor': 'Google'}
)
if response.status == http_client.OK:
content = _from_bytes(content)
return None, content
return None, json.loads(_from_bytes(content))
else:
return response, content
raise AttributeError(
(
'Failed to retrieve {} from the Google Compute Engine'
'metadata service. Response:\n{}'
).format(full_path, response)

This comment was marked as spam.

)

This comment was marked as spam.



def _get_access_token(http_request, email):
token_json = _get_metadata(
'instance'
'service-accounts',
email,
'token',
http_request=http_request
)
return token_json.get('access_token'), int(token_json.get('expires_at'))


class AppAssertionCredentials(AssertionCredentials):
Expand All @@ -86,31 +88,59 @@ class AppAssertionCredentials(AssertionCredentials):
"""

@util.positional(2)
def __init__(self, scope='', **kwargs):
def __init__(self, scope='', service_account_email='default', **kwargs):
"""Constructor for AppAssertionCredentials

Args:
scope: string or iterable of strings, scope(s) of the credentials
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.
scope:
string or iterable of strings, scope(s) of the credentials

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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.
service_account_email:
the email for these credentials. This can be used with custom
service accounts, or left blank to use the default service account
for the instance. Usually the compute engine service account.
"""
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)

This comment was marked as spam.

self.scopes = None

This comment was marked as spam.

self._service_account_email = service_account_email
self._project_id = None

self.kwargs = kwargs

# Assertion type is no longer used, but still in the
# parent class signature.
super(AppAssertionCredentials, self).__init__(None)
self._service_account_email = None

@property
def service_account_email(self):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

if self._service_account_email == 'default':
self._service_account_email = _get_metadata(
'instance',
'service-accounts',
'default',
'email'
)
return self._service_account_email


def _retrieve_scopes(self, http_request):
if not self.scopes:
self.scopes = _get_metadata(
'instance',
'service-accounts',
self._service_account_email,
'scopes',
http_request=http_request
).split('\n')
return self.scopes

@classmethod
def from_json(cls, json_data):
data = json.loads(_from_bytes(json_data))
return AppAssertionCredentials(data['scope'])
return AppAssertionCredentials(email=json_data['email'])

This comment was marked as spam.

This comment was marked as spam.


def _refresh(self, http_request):
"""Refreshes the access_token.
Expand All @@ -125,33 +155,32 @@ def _refresh(self, http_request):
Raises:
HttpAccessTokenRefreshError: When the refresh fails.
"""
response, content = http_request(
META, headers={'Metadata-Flavor': 'Google'})
content = _from_bytes(content)
if response.status == http_client.OK:
try:
token_content = json.loads(content)
except Exception as e:
raise HttpAccessTokenRefreshError(str(e),
status=response.status)
self.access_token = token_content['access_token']
else:
if response.status == http_client.NOT_FOUND:
content += (' This can occur if a VM was created'
' with no service account or scopes.')
raise HttpAccessTokenRefreshError(content, status=response.status)
try:
self.access_token, self.token_expiry = _get_access_token(
http_request,
email=self.service_account_email
)
except Error as e:
raise HttpAccessTokenRefreshError(str(e))

@property
def serialization_data(self):

This comment was marked as spam.

raise NotImplementedError(
'Cannot serialize credentials for GCE service accounts.')
return {
'email': self.service_account_email
}

def create_scoped_required(self):
return False

def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self.kwargs)

@property
def project_id(self):
if not self._project_id:
self._project_id = _get_metadata('project', 'project-id')
return self._project_id

def sign_blob(self, blob):
"""Cryptographically sign a blob (of bytes).

Expand All @@ -166,29 +195,5 @@ def sign_blob(self, blob):
NotImplementedError, always.
"""
raise NotImplementedError(
'Compute Engine service accounts cannot sign blobs')

@property
def service_account_email(self):
"""Get the email for the current service account.

Uses the Google Compute Engine metadata service to retrieve the email
of the default service account.

Returns:
string, The email associated with the Google Compute Engine
service account.

Raises:
AttributeError, if the email can not be retrieved from the Google
Compute Engine metadata service.
"""
if self._service_account_email is None:
failure, email = _get_service_account_email()
if failure is None:
self._service_account_email = email
else:
raise AttributeError('Failed to retrieve the email from the '
'Google Compute Engine metadata service',
failure, email)
return self._service_account_email
'Compute Engine service accounts cannot sign blobs'
)

This comment was marked as spam.