Uh oh!
There was an error while loading. Please reload this page.
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
- Notifications
You must be signed in to change notification settings - Fork 173
feature: V4 Post policies#87
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
447310f
feat: add POST policies building method
fa09133
add comments, ignoring x-ignore fields and required fields validation
6818fb6
fix docs style, add virtual hosted style URLs
c216a59
add bucket_bound_hostname support
b01c801
cosmetic changes
f56440b
add unit tests
dc7028d
Revert "add unit tests"
0a442b4
add few lines from the old implementation for consistency
4982ad7
add some system tests
333e212
move system tests into separate class
173c3f6
fix credentials scope URL mistake
1c80907
fix unit tests
3f15f2e
fix algorithm name
9acc3d7
add an example
b60c45e
add access token support
77ec997
Merge branch 'master' into v4_post_policies
frankyn b6c4106
add credentials as an argument
32b2f73
Merge branch 'v4_post_policies' of https://github.com/q-logic/python-…
51469a8
rename method
ade9985
add conformance tests into client unit tests
f62c48e
align conformance tests with test data
0572627
add an ability to set expiration as integer
edf84da
Merge branch 'v4_post_policies' into post_policy_conformance_tests
a630984
update conformance tests to avoid problems with json spaces and times…
d0028c9
update implementation to avoid Z symbol isoformat violation and json …
1de8570
Merge branch 'v4_post_policies' into post_policy_conformance_tests
c8bce87
fix error with bounded hostnames
7ecf43d
fix problem with bounded hostnames in implementation
353e4e2
Merge branch 'v4_post_policies' into post_policy_conformance_tests
1c87755
fix conformance tests
6aa3ea5
fix problems: ascii encoding of signature and fields order
0b14b3b
Merge branch 'v4_post_policies' into post_policy_conformance_tests
2597d29
change asserts order
7c4bd6c
fix conformance tests
55a6d42
fix encoding issues
2fd0025
merge unit test
8506d83
cosmetic changes and adding conformance tests
fd31846
fix russion "C" letter in comment
9048cc5
add conformance tests data
81759b2
cosmetic changes
8fcb8bf
cosmetic changes
7b1406e
add fields sorting
16daedd
fix system tests
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -14,18 +14,28 @@ | ||
| """Client for interacting with the Google Cloud Storage API.""" | ||
| import warnings | ||
| import base64 | ||
| import binascii | ||
| import datetime | ||
| import functools | ||
| import json | ||
| import warnings | ||
| import google.api_core.client_options | ||
| from google.auth.credentials import AnonymousCredentials | ||
| from google.api_core import page_iterator | ||
| from google.cloud._helpers import _LocalStack | ||
| from google.cloud._helpers import _LocalStack, _NOW | ||
| from google.cloud.client import ClientWithProject | ||
| from google.cloud.exceptions import NotFound | ||
| from google.cloud.storage._helpers import _get_storage_host | ||
| from google.cloud.storage._http import Connection | ||
| from google.cloud.storage._signing import ( | ||
| get_expiration_seconds_v4, | ||
| get_v4_now_dtstamps, | ||
| ensure_signed_credentials, | ||
| _sign_message, | ||
| ) | ||
| from google.cloud.storage.batch import Batch | ||
| from google.cloud.storage.bucket import Bucket | ||
| from google.cloud.storage.blob import Blob | ||
| @@ -836,6 +846,174 @@ def get_hmac_key_metadata( | ||
| metadata.reload(timeout=timeout) # raises NotFound for missing key | ||
| return metadata | ||
| def generate_signed_post_policy_v4( | ||
| self, | ||
| bucket_name, | ||
| blob_name, | ||
| expiration, | ||
IlyaFaer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| conditions=None, | ||
| fields=None, | ||
| credentials=None, | ||
| virtual_hosted_style=False, | ||
| bucket_bound_hostname=None, | ||
| scheme=None, | ||
IlyaFaer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| service_account_email=None, | ||
| access_token=None, | ||
| ): | ||
| """Generate a V4 signed policy object. | ||
IlyaFaer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| .. note:: | ||
| Assumes ``credentials`` implements the | ||
| :class:`google.auth.credentials.Signing` interface. Also assumes | ||
| ``credentials`` has a ``service_account_email`` property which | ||
| identifies the credentials. | ||
| Generated policy object allows user to upload objects with a POST request. | ||
| :type bucket_name: str | ||
| :param bucket_name: Bucket name. | ||
| :type blob_name: str | ||
| :param blob_name: Object name. | ||
| :type expiration: Union[Integer, datetime.datetime, datetime.timedelta] | ||
| :param expiration: Policy expiration time. | ||
| :type conditions: list | ||
| :param conditions: (Optional) List of POST policy conditions, which are | ||
| used to restrict what is allowed in the request. | ||
| :type fields: dict | ||
| :param fields: (Optional) Additional elements to include into request. | ||
| :type credentials: :class:`google.auth.credentials.Signing` | ||
| :param credentials: (Optional) Credentials object with an associated private | ||
| key to sign text. | ||
| :type virtual_hosted_style: bool | ||
| :param virtual_hosted_style: (Optional) If True, construct the URL relative to the bucket | ||
| virtual hostname, e.g., '<bucket-name>.storage.googleapis.com'. | ||
| :type bucket_bound_hostname: str | ||
| :param bucket_bound_hostname: | ||
| (Optional) If passed, construct the URL relative to the bucket-bound hostname. | ||
| Value can be bare or with a scheme, e.g., 'example.com' or 'http://example.com'. | ||
| See: https://cloud.google.com/storage/docs/request-endpoints#cname | ||
| :type scheme: str | ||
| :param scheme: | ||
| (Optional) If ``bucket_bound_hostname`` is passed as a bare hostname, use | ||
| this value as a scheme. ``https`` will work only when using a CDN. | ||
| Defaults to ``"http"``. | ||
| :type service_account_email: str | ||
| :param service_account_email: (Optional) E-mail address of the service account. | ||
| :type access_token: str | ||
| :param access_token: (Optional) Access token for a service account. | ||
| :rtype: dict | ||
| :returns: Signed POST policy. | ||
| Example: | ||
| Generate signed POST policy and upload a file. | ||
| >>> from google.cloud import storage | ||
| >>> client = storage.Client() | ||
| >>> policy = client.generate_signed_post_policy_v4( | ||
| "bucket-name", | ||
| "blob-name", | ||
| expiration=datetime.datetime(2020, 3, 17), | ||
| conditions=[ | ||
| ["content-length-range", 0, 255] | ||
| ], | ||
| fields=[ | ||
| "x-goog-meta-hello" => "world" | ||
| ], | ||
| ) | ||
| >>> with open("bucket-name", "rb") as f: | ||
| files = {"file": ("bucket-name", f)} | ||
| requests.post(policy["url"], data=policy["fields"], files=files) | ||
IlyaFaer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| """ | ||
| credentials = self._credentials if credentials is None else credentials | ||
| ensure_signed_credentials(credentials) | ||
| # prepare policy conditions and fields | ||
| timestamp, datestamp = get_v4_now_dtstamps() | ||
| x_goog_credential = "{email}/{datestamp}/auto/storage/goog4_request".format( | ||
| email=credentials.signer_email, datestamp=datestamp | ||
| ) | ||
| required_conditions = [ | ||
| {"key": blob_name}, | ||
| {"x-goog-date": timestamp}, | ||
| {"x-goog-credential": x_goog_credential}, | ||
| {"x-goog-algorithm": "GOOG4-RSA-SHA256"}, | ||
| ] | ||
| conditions = conditions or [] | ||
| policy_fields = {} | ||
| for key, value in sorted((fields or {}).items()): | ||
| if not key.startswith("x-ignore-"): | ||
| policy_fields[key] = value | ||
| conditions.append({key: value}) | ||
| conditions += required_conditions | ||
| # calculate policy expiration time | ||
| now = _NOW() | ||
| if expiration is None: | ||
| expiration = now + datetime.timedelta(hours=1) | ||
| policy_expires = now + datetime.timedelta( | ||
| seconds=get_expiration_seconds_v4(expiration) | ||
| ) | ||
| # encode policy for signing | ||
| policy = json.dumps( | ||
| {"conditions": conditions, "expiration": policy_expires.isoformat() + "Z"}, | ||
| separators=(",", ":"), | ||
| ) | ||
| str_to_sign = base64.b64encode(policy.encode("utf-8")) | ||
| # sign the policy and get its cryptographic signature | ||
| if access_token and service_account_email: | ||
| signature = _sign_message(str_to_sign, access_token, service_account_email) | ||
| signature_bytes = base64.b64decode(signature) | ||
| else: | ||
| signature_bytes = credentials.sign_bytes(str_to_sign) | ||
IlyaFaer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| # get hexadecimal representation of the signature | ||
| signature = binascii.hexlify(signature_bytes).decode("utf-8") | ||
| policy_fields.update( | ||
| { | ||
| "key": blob_name, | ||
| "x-goog-algorithm": "GOOG4-RSA-SHA256", | ||
| "x-goog-credential": x_goog_credential, | ||
| "x-goog-date": timestamp, | ||
| "x-goog-signature": signature, | ||
| "policy": str_to_sign, | ||
| } | ||
| ) | ||
| # designate URL | ||
| if virtual_hosted_style: | ||
| url = "https://{}.storage.googleapis.com/".format(bucket_name) | ||
| elif bucket_bound_hostname: | ||
| if ":" in bucket_bound_hostname: # URL includes scheme | ||
| url = bucket_bound_hostname | ||
| else: # scheme is given separately | ||
| url = "{scheme}://{host}/".format( | ||
| scheme=scheme, host=bucket_bound_hostname | ||
| ) | ||
| else: | ||
| url = "https://storage.googleapis.com/{}/".format(bucket_name) | ||
| return {"url": url, "fields": policy_fields} | ||
| def _item_to_bucket(iterator, item): | ||
| """Convert a JSON bucket to the native object. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.