2929from oauth2client import service_account
3030import pytz
3131
32+ try:
33+ from google.appengine.api import app_identity
34+ except ImportError:
35+ app_identity = None
36+
37+ try:
38+ from oauth2client.appengine import AppAssertionCredentials as _GAECreds
39+ except ImportError:
40+ class _GAECreds(object):
41+ """Dummy class if not in App Engine environment."""
42+
3243
3344def get_credentials():
3445 """Gets credentials implicitly from the current environment.
@@ -160,7 +171,66 @@ def _get_pem_key(credentials):
160171 return RSA.importKey(pem_text)
161172
162173
163- def _get_signed_query_params(credentials, expiration, signature_string):
174+ def _get_signature_bytes(credentials, string_to_sign):
175+ """Uses crypto attributes of credentials to sign a string/bytes.
176+
177+ :type credentials: :class:`client.SignedJwtAssertionCredentials`,
178+ :class:`service_account._ServiceAccountCredentials`,
179+ :class:`_GAECreds`
180+ :param credentials: The credentials used for signing text (typically
181+ involves the creation of an RSA key).
182+
183+ :type string_to_sign: string
184+ :param string_to_sign: The string to be signed by the credentials.
185+
186+ :rtype: bytes
187+ :returns: Signed bytes produced by the credentials.
188+ """
189+ if isinstance(credentials, _GAECreds):
190+ _, signed_bytes = app_identity.sign_blob(string_to_sign)
191+ return signed_bytes
192+ else:
193+ pem_key = _get_pem_key(credentials)
194+ # Sign the string with the RSA key.
195+ signer = PKCS1_v1_5.new(pem_key)
196+ if not isinstance(string_to_sign, six.binary_type):
197+ string_to_sign = string_to_sign.encode('utf-8')
198+ signature_hash = SHA256.new(string_to_sign)
199+ return signer.sign(signature_hash)
200+
201+
202+ def _get_service_account_name(credentials):
203+ """Determines service account name from a credentials object.
204+
205+ :type credentials: :class:`client.SignedJwtAssertionCredentials`,
206+ :class:`service_account._ServiceAccountCredentials`,
207+ :class:`_GAECreds`
208+ :param credentials: The credentials used to determine the service
209+ account name.
210+
211+ :type string_to_sign: string
212+ :param string_to_sign: The string to be signed by the credentials.
213+
214+ :rtype: bytes
215+ :returns: Signed bytes produced by the credentials.
216+ :raises: :class:`ValueError` if the credentials are not a valid service
217+ account type
218+ """
219+ service_account_name = None
220+ if isinstance(credentials, client.SignedJwtAssertionCredentials):
221+ service_account_name = credentials.service_account_name
222+ elif isinstance(credentials, service_account._ServiceAccountCredentials):
223+ service_account_name = credentials._service_account_email
224+ elif _GAECreds is not None and isinstance(credentials, _GAECreds):
225+ service_account_name = app_identity.get_service_account_name()
226+
227+ if service_account_name is None:
228+ raise ValueError('Service account name could not be determined '
229+ 'from credentials')
230+ return service_account_name
231+
232+
233+ def _get_signed_query_params(credentials, expiration, string_to_sign):
164234 """Gets query parameters for creating a signed URL.
165235
166236 :type credentials: :class:`client.SignedJwtAssertionCredentials`,
@@ -171,27 +241,16 @@ def _get_signed_query_params(credentials, expiration, signature_string):
171241 :type expiration: int or long
172242 :param expiration: When the signed URL should expire.
173243
174- :type signature_string : string
175- :param signature_string : The string to be signed by the credentials.
244+ :type string_to_sign : string
245+ :param string_to_sign : The string to be signed by the credentials.
176246
177247 :rtype: dict
178248 :returns: Query parameters matching the signing credentials with a
179249 signed payload.
180250 """
181- pem_key = _get_pem_key(credentials)
182- # Sign the string with the RSA key.
183- signer = PKCS1_v1_5.new(pem_key)
184- if not isinstance(signature_string, six.binary_type):
185- signature_string = signature_string.encode('utf-8')
186- signature_hash = SHA256.new(signature_string)
187- signature_bytes = signer.sign(signature_hash)
251+ signature_bytes = _get_signature_bytes(credentials, string_to_sign)
188252 signature = base64.b64encode(signature_bytes)
189-
190- if isinstance(credentials, client.SignedJwtAssertionCredentials):
191- service_account_name = credentials.service_account_name
192- elif isinstance(credentials, service_account._ServiceAccountCredentials):
193- service_account_name = credentials._service_account_email
194- # We know one of the above must occur since `_get_pem_key` fails if not.
253+ service_account_name = _get_service_account_name(credentials)
195254 return {
196255 'GoogleAccessId': service_account_name,
197256 'Expires': str(expiration),
@@ -277,7 +336,7 @@ def generate_signed_url(credentials, resource, expiration,
277336 expiration = _get_expiration_seconds(expiration)
278337
279338 # Generate the string to sign.
280- signature_string = '\n'.join([
339+ string_to_sign = '\n'.join([
281340 method,
282341 content_md5 or '',
283342 content_type or '',
@@ -287,7 +346,7 @@ def generate_signed_url(credentials, resource, expiration,
287346 # Set the right query parameters.
288347 query_params = _get_signed_query_params(credentials,
289348 expiration,
290- signature_string )
349+ string_to_sign )
291350
292351 # Return the built URL.
293352 return '{endpoint}{resource}?{querystring}'.format(
0 commit comments