Skip to content

Commit

Permalink
Merge pull request #1409 from dhermes/generate-signed-url-extras
Browse files Browse the repository at this point in the history
Adding optional arguments to generate signed URI method.
  • Loading branch information
dhermes committed Jan 28, 2016
2 parents 7b08a59 + f444fca commit 2c94475
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 32 deletions.
53 changes: 40 additions & 13 deletions gcloud/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,15 +294,22 @@ def _get_expiration_seconds(expiration):
def generate_signed_url(credentials, resource, expiration,
api_access_endpoint='',
method='GET', content_md5=None,
content_type=None):
content_type=None, response_type=None,
response_disposition=None, generation=None):
"""Generate signed URL to provide query-string auth'n to a resource.
.. note::
If you are on Google Compute Engine, you can't generate a signed URL.
Follow https://github.com/GoogleCloudPlatform/gcloud-python/issues/922
for updates on this. If you'd like to be able to generate a signed URL
from GCE, you can use a standard service account from a JSON file
rather than a GCE service account.
If you are on Google Compute Engine, you can't generate a signed URL.
Follow `Issue 922`_ for updates on this. If you'd like to be able to
generate a signed URL from GCE, you can use a standard service account
from a JSON file rather than a GCE service account.
See headers `reference`_ for more details on optional arguments.
.. _Issue 922: https://github.com/GoogleCloudPlatform/\
gcloud-python/issues/922
.. _reference: https://cloud.google.com/storage/docs/reference-headers
:type credentials: :class:`oauth2client.appengine.AppAssertionCredentials`
:param credentials: Credentials object with an associated private key to
Expand All @@ -316,19 +323,33 @@ def generate_signed_url(credentials, resource, expiration,
:class:`datetime.timedelta`
:param expiration: When the signed URL should expire.
:type api_access_endpoint: string
:type api_access_endpoint: str
:param api_access_endpoint: Optional URI base. Defaults to empty string.
:type method: string
:type method: str
:param method: The HTTP verb that will be used when requesting the URL.
Defaults to ``'GET'``.
:type content_md5: string
:param content_md5: The MD5 hash of the object referenced by
:type content_md5: str
:param content_md5: (Optional) The MD5 hash of the object referenced by
``resource``.
:type content_type: string
:param content_type: The content type of the object referenced by
``resource``.
:type content_type: str
:param content_type: (Optional) The content type of the object referenced
by ``resource``.
:type response_type: str
:param response_type: (Optional) Content type of responses to requests for
the signed URL. Used to over-ride the content type of
the underlying resource.
:type response_disposition: str
:param response_disposition: (Optional) Content disposition of responses to
requests for the signed URL.
:type generation: str
:param generation: (Optional) A value that indicates which generation of
the resource to fetch.
:rtype: string
:returns: A signed URL you can use to access the resource
Expand All @@ -348,6 +369,12 @@ def generate_signed_url(credentials, resource, expiration,
query_params = _get_signed_query_params(credentials,
expiration,
string_to_sign)
if response_type is not None:
query_params['response-content-type'] = response_type
if response_disposition is not None:
query_params['response-content-disposition'] = response_disposition
if generation is not None:
query_params['generation'] = generation

# Return the built URL.
return '{endpoint}{resource}?{querystring}'.format(
Expand Down
52 changes: 39 additions & 13 deletions gcloud/storage/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,16 +150,20 @@ def public_url(self):
quoted_name=quote(self.name, safe=''))

def generate_signed_url(self, expiration, method='GET',
client=None, credentials=None):
client=None, credentials=None,
response_type=None, response_disposition=None,
generation=None):
"""Generates a signed URL for this blob.
.. note::
If you are on Google Compute Engine, you can't generate a signed URL.
Follow
https://github.com/GoogleCloudPlatform/gcloud-python/issues/922
for updates on this. If you'd like to be able to generate a signed
URL from GCE, you can use a standard service account from a JSON
file rather than a GCE service account.
If you are on Google Compute Engine, you can't generate a signed
URL. Follow `Issue 922`_ for updates on this. If you'd like to
be able to generate a signed URL from GCE, you can use a standard
service account from a JSON file rather than a GCE service account.
.. _Issue 922: https://github.com/GoogleCloudPlatform/\
gcloud-python/issues/922
If you have a blob that you want to allow access to for a set
amount of time, you can use this method to generate a URL that
Expand All @@ -172,18 +176,37 @@ def generate_signed_url(self, expiration, method='GET',
:type expiration: int, long, datetime.datetime, datetime.timedelta
:param expiration: When the signed URL should expire.
:type method: string
:type method: str
:param method: The HTTP verb that will be used when requesting the URL.
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
:param client: Optional. The client to use. If not passed, falls back
:param client: (Optional) The client to use. If not passed, falls back
to the ``client`` stored on the blob's bucket.
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
:class:`NoneType`
:param credentials: The OAuth2 credentials to use to sign the URL.
:rtype: string
:param credentials: (Optional) The OAuth2 credentials to use to sign
the URL. Defaults to the credentials stored on the
client used.
:type response_type: str
:param response_type: (Optional) Content type of responses to requests
for the signed URL. Used to over-ride the content
type of the underlying blob/object.
:type response_disposition: str
:param response_disposition: (Optional) Content disposition of
responses to requests for the signed URL.
For example, to enable the signed URL
to initiate a file of ``blog.png``, use
the value
``'attachment; filename=blob.png'``.
:type generation: str
:param generation: (Optional) A value that indicates which generation
of the resource to fetch.
:rtype: str
:returns: A signed URL you can use to access the resource
until expiration.
"""
Expand All @@ -198,7 +221,10 @@ def generate_signed_url(self, expiration, method='GET',
return generate_signed_url(
credentials, resource=resource,
api_access_endpoint=_API_ACCESS_ENDPOINT,
expiration=expiration, method=method)
expiration=expiration, method=method,
response_type=response_type,
response_disposition=response_disposition,
generation=generation)

def exists(self, client=None):
"""Determines whether or not this blob exists.
Expand Down
9 changes: 9 additions & 0 deletions gcloud/storage/test_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ def _basic_generate_signed_url_helper(self, credentials=None):
'expiration': EXPIRATION,
'method': 'GET',
'resource': PATH,
'response_type': None,
'response_disposition': None,
'generation': None,
}
self.assertEqual(SIGNER._signed, [(EXPECTED_ARGS, EXPECTED_KWARGS)])

Expand Down Expand Up @@ -180,6 +183,9 @@ def test_generate_signed_url_w_slash_in_name(self):
'expiration': EXPIRATION,
'method': 'GET',
'resource': '/name/parent%2Fchild',
'response_type': None,
'response_disposition': None,
'generation': None,
}
self.assertEqual(SIGNER._signed, [(EXPECTED_ARGS, EXPECTED_KWARGS)])

Expand Down Expand Up @@ -208,6 +214,9 @@ def test_generate_signed_url_w_method_arg(self):
'expiration': EXPIRATION,
'method': 'POST',
'resource': PATH,
'response_type': None,
'response_disposition': None,
'generation': None,
}
self.assertEqual(SIGNER._signed, [(EXPECTED_ARGS, EXPECTED_KWARGS)])

Expand Down
36 changes: 30 additions & 6 deletions gcloud/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ def _callFUT(self, *args, **kwargs):
from gcloud.credentials import generate_signed_url
return generate_signed_url(*args, **kwargs)

def test_w_expiration_int(self):
def _generate_helper(self, response_type=None, response_disposition=None,
generation=None):
import base64
from six.moves.urllib.parse import parse_qs
from six.moves.urllib.parse import urlsplit
Expand All @@ -209,21 +210,44 @@ def _get_signed_query_params(*args):

with _Monkey(MUT, _get_signed_query_params=_get_signed_query_params):
url = self._callFUT(CREDENTIALS, RESOURCE, 1000,
api_access_endpoint=ENDPOINT)
api_access_endpoint=ENDPOINT,
response_type=response_type,
response_disposition=response_disposition,
generation=generation)

scheme, netloc, path, qs, frag = urlsplit(url)
self.assertEqual(scheme, 'http')
self.assertEqual(netloc, 'api.example.com')
self.assertEqual(path, RESOURCE)
params = parse_qs(qs)
self.assertEqual(len(params), 3)
# In Py3k, parse_qs gives us text values:
self.assertEqual(params['Signature'], [SIGNED.decode('ascii')])
self.assertEqual(params['Expires'], ['1000'])
self.assertEqual(params['GoogleAccessId'],
self.assertEqual(params.pop('Signature'), [SIGNED.decode('ascii')])
self.assertEqual(params.pop('Expires'), ['1000'])
self.assertEqual(params.pop('GoogleAccessId'),
[_Credentials.service_account_name])
if response_type is not None:
self.assertEqual(params.pop('response-content-type'),
[response_type])
if response_disposition is not None:
self.assertEqual(params.pop('response-content-disposition'),
[response_disposition])
if generation is not None:
self.assertEqual(params.pop('generation'), [generation])
# Make sure we have checked them all.
self.assertEqual(len(params), 0)
self.assertEqual(frag, '')

def test_w_expiration_int(self):
self._generate_helper()

def test_w_custom_fields(self):
response_type = 'text/plain'
response_disposition = 'attachment; filename=blob.png'
generation = '123'
self._generate_helper(response_type=response_type,
response_disposition=response_disposition,
generation=generation)


class Test__get_signature_bytes(unittest2.TestCase):

Expand Down

0 comments on commit 2c94475

Please sign in to comment.