Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(storage): Add cname support for V4 signature #72

Merged
merged 14 commits into from Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion google/cloud/storage/_signing.py
Expand Up @@ -553,7 +553,7 @@ def generate_signed_url_v4(

header_names = [key.lower() for key in headers]
if "host" not in header_names:
headers["Host"] = "storage.googleapis.com"
headers["Host"] = six.moves.urllib.parse.urlparse(api_access_endpoint).netloc

if method.upper() == "RESUMABLE":
method = "POST"
Expand Down
14 changes: 13 additions & 1 deletion google/cloud/storage/blob.py
Expand Up @@ -362,6 +362,7 @@ def generate_signed_url(
service_account_email=None,
access_token=None,
virtual_hosted_style=False,
use_cname=None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use parameter name: bucket_bound_host_name. I prefer that this parameter be passed in a string and is an alias for api_access_endpoint=. I'd prefer not required two parameters to set bucket_bound_host_name as enabled.

):
"""Generates a signed URL for this blob.

Expand All @@ -384,6 +385,8 @@ def generate_signed_url(
accessible blobs, but don't want to require users to explicitly
log in.

If ``cname`` is set as an argument of :attr:`api_access_endpoint`, ``https`` works only if using a ``CDN``.

:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.

Expand Down Expand Up @@ -460,6 +463,13 @@ def generate_signed_url(
(Optional) If true, then construct the URL relative the bucket's
virtual hostname, e.g., '<bucket-name>.storage.googleapis.com'.

:type use_cname: bool
:param use_cname:
(Optional) If true, then construct the URL relative the bucket-bound hostname,
pass ``bucket-bound hostname`` value as argument of ``api_access_endpoint``,
e.g., 'api_access_endpoint = <bucket-bound hostname>'. See:
https://cloud.google.com/storage/docs/request-endpoints#cname

:raises: :exc:`ValueError` when version is invalid.
:raises: :exc:`TypeError` when expiration is not a valid type.
:raises: :exc:`AttributeError` if credentials is not an instance
Expand All @@ -480,12 +490,14 @@ def generate_signed_url(
api_access_endpoint = "https://{bucket_name}.storage.googleapis.com".format(
bucket_name=self.bucket.name
)
resource = "/{quoted_name}".format(quoted_name=quoted_name)
else:
resource = "/{bucket_name}/{quoted_name}".format(
bucket_name=self.bucket.name, quoted_name=quoted_name
)

if virtual_hosted_style or use_cname:
resource = "/{quoted_name}".format(quoted_name=quoted_name)

if credentials is None:
client = self._require_client(client)
credentials = client._credentials
Expand Down
15 changes: 14 additions & 1 deletion google/cloud/storage/bucket.py
Expand Up @@ -2355,6 +2355,7 @@ def generate_signed_url(
credentials=None,
version=None,
virtual_hosted_style=False,
use_cname=None,
):
"""Generates a signed URL for this bucket.

Expand All @@ -2373,6 +2374,8 @@ def generate_signed_url(
amount of time, you can use this method to generate a URL that
is only valid within a certain time period.

If ``cname`` is set as an argument of :attr:`api_access_endpoint`, ``https`` works only if using a ``CDN``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cname no longer used in method signature should be updated.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an inline example of using bucket_bound_hostname and scheme

This is particularly useful if you don't want publicly
accessible buckets, but don't want to require users to explicitly
log in.
Expand Down Expand Up @@ -2422,6 +2425,13 @@ def generate_signed_url(
(Optional) If true, then construct the URL relative the bucket's
virtual hostname, e.g., '<bucket-name>.storage.googleapis.com'.

:type use_cname: bool
:param use_cname:
(Optional) If true, then construct the URL relative the bucket-bound hostname ,
pass ``bucket-bound hostname`` value as argument of ``api_access_endpoint``,
e.g., 'api_access_endpoint = <bucket-bound hostname>'. See:
https://cloud.google.com/storage/docs/request-endpoints#cname

:raises: :exc:`ValueError` when version is invalid.
:raises: :exc:`TypeError` when expiration is not a valid type.
:raises: :exc:`AttributeError` if credentials is not an instance
Expand All @@ -2440,10 +2450,13 @@ def generate_signed_url(
api_access_endpoint = "https://{bucket_name}.storage.googleapis.com".format(
bucket_name=self.name
)
resource = "/"

else:
resource = "/{bucket_name}".format(bucket_name=self.name)

if virtual_hosted_style or use_cname:
resource = "/"

if credentials is None:
client = self._require_client(client)
credentials = client._credentials
Expand Down
11 changes: 10 additions & 1 deletion tests/unit/test_blob.py
Expand Up @@ -400,6 +400,7 @@ def _generate_signed_url_helper(
access_token=None,
service_account_email=None,
virtual_hosted_style=False,
use_cname=None,
):
from six.moves.urllib import parse
from google.cloud._helpers import UTC
Expand Down Expand Up @@ -444,6 +445,7 @@ def _generate_signed_url_helper(
access_token=access_token,
service_account_email=service_account_email,
virtual_hosted_style=virtual_hosted_style,
use_cname=use_cname,
)

self.assertEqual(signed_uri, signer.return_value)
Expand All @@ -460,11 +462,13 @@ def _generate_signed_url_helper(
expected_api_access_endpoint = "https://{}.storage.googleapis.com".format(
bucket.name
)
expected_resource = "/{}".format(quoted_name)
else:
expected_api_access_endpoint = api_access_endpoint
expected_resource = "/{}/{}".format(bucket.name, quoted_name)

if virtual_hosted_style or use_cname:
expected_resource = "/{}".format(quoted_name)

if encryption_key is not None:
expected_headers = headers or {}
if effective_version == "v2":
Expand Down Expand Up @@ -619,6 +623,11 @@ def test_generate_signed_url_v4_w_csek_and_headers(self):
def test_generate_signed_url_v4_w_virtual_hostname(self):
self._generate_signed_url_v4_helper(virtual_hosted_style=True)

def test_generate_signed_url_v4_w_bucket_bound_hostname(self):
self._generate_signed_url_v4_helper(
api_access_endpoint="https://cdn.example.com", use_cname=True
)

def test_generate_signed_url_v4_w_credentials(self):
credentials = object()
self._generate_signed_url_v4_helper(credentials=credentials)
Expand Down
11 changes: 10 additions & 1 deletion tests/unit/test_bucket.py
Expand Up @@ -2740,6 +2740,7 @@ def _generate_signed_url_helper(
credentials=None,
expiration=None,
virtual_hosted_style=False,
use_cname=None,
):
from six.moves.urllib import parse
from google.cloud._helpers import UTC
Expand Down Expand Up @@ -2775,6 +2776,7 @@ def _generate_signed_url_helper(
query_parameters=query_parameters,
version=version,
virtual_hosted_style=virtual_hosted_style,
use_cname=use_cname,
)

self.assertEqual(signed_uri, signer.return_value)
Expand All @@ -2788,11 +2790,13 @@ def _generate_signed_url_helper(
expected_api_access_endpoint = "https://{}.storage.googleapis.com".format(
bucket_name
)
expected_resource = "/"
else:
expected_api_access_endpoint = api_access_endpoint
expected_resource = "/{}".format(parse.quote(bucket_name))

if virtual_hosted_style or use_cname:
expected_resource = "/"

expected_kwargs = {
"resource": expected_resource,
"expiration": expiration,
Expand Down Expand Up @@ -2928,6 +2932,11 @@ def test_generate_signed_url_v4_w_credentials(self):
def test_generate_signed_url_v4_w_virtual_hostname(self):
self._generate_signed_url_v4_helper(virtual_hosted_style=True)

def test_generate_signed_url_v4_w_bucket_bound_hostname(self):
self._generate_signed_url_v4_helper(
api_access_endpoint="https://cdn.example.com", use_cname=True
)


class _Connection(object):
_delete_bucket = False
Expand Down