Skip to content

Commit 8ec02c0

Browse files
authored
fix: allow signed post policy v4 with service account and token (#1356)
1 parent cea20e2 commit 8ec02c0

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

google/cloud/storage/client.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1724,13 +1724,16 @@ def generate_signed_post_policy_v4(
17241724
)
17251725

17261726
credentials = self._credentials if credentials is None else credentials
1727-
ensure_signed_credentials(credentials)
1727+
client_email = service_account_email
1728+
if not access_token or not service_account_email:
1729+
ensure_signed_credentials(credentials)
1730+
client_email = credentials.signer_email
17281731

17291732
# prepare policy conditions and fields
17301733
timestamp, datestamp = get_v4_now_dtstamps()
17311734

17321735
x_goog_credential = "{email}/{datestamp}/auto/storage/goog4_request".format(
1733-
email=credentials.signer_email, datestamp=datestamp
1736+
email=client_email, datestamp=datestamp
17341737
)
17351738
required_conditions = [
17361739
{"bucket": bucket_name},

tests/system/test__signing.py

+49
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,55 @@ def test_generate_signed_post_policy_v4(
415415
assert blob.download_as_bytes() == payload
416416

417417

418+
@pytest.mark.skipif(
419+
_helpers.is_api_endpoint_override,
420+
reason="Test does not yet support endpoint override",
421+
)
422+
def test_generate_signed_post_policy_v4_access_token_sa_email(
423+
storage_client, signing_bucket, blobs_to_delete, service_account, no_mtls
424+
):
425+
client = iam_credentials_v1.IAMCredentialsClient()
426+
service_account_email = service_account.service_account_email
427+
name = path_template.expand(
428+
"projects/{project}/serviceAccounts/{service_account}",
429+
project="-",
430+
service_account=service_account_email,
431+
)
432+
scope = [
433+
"https://www.googleapis.com/auth/devstorage.read_write",
434+
"https://www.googleapis.com/auth/iam",
435+
]
436+
response = client.generate_access_token(name=name, scope=scope)
437+
438+
now = _NOW(_UTC).replace(tzinfo=None)
439+
blob_name = "post_policy_obj_email2.txt"
440+
payload = b"DEADBEEF"
441+
with open(blob_name, "wb") as f:
442+
f.write(payload)
443+
policy = storage_client.generate_signed_post_policy_v4(
444+
signing_bucket.name,
445+
blob_name,
446+
conditions=[
447+
{"bucket": signing_bucket.name},
448+
["starts-with", "$Content-Type", "text/pla"],
449+
],
450+
expiration=now + datetime.timedelta(hours=1),
451+
fields={"content-type": "text/plain"},
452+
service_account_email=service_account_email,
453+
access_token=response.access_token,
454+
)
455+
with open(blob_name, "r") as f:
456+
files = {"file": (blob_name, f)}
457+
response = requests.post(policy["url"], data=policy["fields"], files=files)
458+
459+
os.remove(blob_name)
460+
assert response.status_code == 204
461+
462+
blob = signing_bucket.get_blob(blob_name)
463+
blobs_to_delete.append(blob)
464+
assert blob.download_as_bytes() == payload
465+
466+
418467
def test_generate_signed_post_policy_v4_invalid_field(
419468
storage_client, buckets_to_delete, blobs_to_delete, service_account, no_mtls
420469
):

tests/unit/test_client.py

+44
Original file line numberDiff line numberDiff line change
@@ -2849,6 +2849,50 @@ def test_get_signed_policy_v4_with_access_token(self):
28492849
self.assertEqual(fields["x-goog-signature"], EXPECTED_SIGN)
28502850
self.assertEqual(fields["policy"], EXPECTED_POLICY)
28512851

2852+
def test_get_signed_policy_v4_with_access_token_sa_email(self):
2853+
import datetime
2854+
2855+
BUCKET_NAME = "bucket-name"
2856+
BLOB_NAME = "object-name"
2857+
EXPECTED_SIGN = "0c4003044105"
2858+
EXPECTED_POLICY = "eyJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJidWNrZXQtbmFtZSJ9LHsiYWNsIjoicHJpdmF0ZSJ9LFsic3RhcnRzLXdpdGgiLCIkQ29udGVudC1UeXBlIiwidGV4dC9wbGFpbiJdLHsiYnVja2V0IjoiYnVja2V0LW5hbWUifSx7ImtleSI6Im9iamVjdC1uYW1lIn0seyJ4LWdvb2ctZGF0ZSI6IjIwMjAwMzEyVDExNDcxNloifSx7IngtZ29vZy1jcmVkZW50aWFsIjoidGVzdEBtYWlsLmNvbS8yMDIwMDMxMi9hdXRvL3N0b3JhZ2UvZ29vZzRfcmVxdWVzdCJ9LHsieC1nb29nLWFsZ29yaXRobSI6IkdPT0c0LVJTQS1TSEEyNTYifV0sImV4cGlyYXRpb24iOiIyMDIwLTAzLTI2VDAwOjAwOjEwWiJ9"
2859+
2860+
project = "PROJECT"
2861+
credentials = _make_credentials(project=project)
2862+
client = self._make_one(credentials=credentials)
2863+
2864+
dtstamps_patch, now_patch, expire_secs_patch = _time_functions_patches()
2865+
with dtstamps_patch, now_patch, expire_secs_patch:
2866+
with mock.patch(
2867+
"google.cloud.storage.client._sign_message", return_value=b"DEADBEEF"
2868+
):
2869+
policy = client.generate_signed_post_policy_v4(
2870+
BUCKET_NAME,
2871+
BLOB_NAME,
2872+
expiration=datetime.datetime(2020, 3, 12),
2873+
conditions=[
2874+
{"bucket": BUCKET_NAME},
2875+
{"acl": "private"},
2876+
["starts-with", "$Content-Type", "text/plain"],
2877+
],
2878+
service_account_email="[email protected]",
2879+
access_token="token",
2880+
)
2881+
self.assertEqual(
2882+
policy["url"], "https://storage.googleapis.com/" + BUCKET_NAME + "/"
2883+
)
2884+
fields = policy["fields"]
2885+
2886+
self.assertEqual(fields["key"], BLOB_NAME)
2887+
self.assertEqual(fields["x-goog-algorithm"], "GOOG4-RSA-SHA256")
2888+
self.assertEqual(fields["x-goog-date"], "20200312T114716Z")
2889+
self.assertEqual(
2890+
fields["x-goog-credential"],
2891+
"[email protected]/20200312/auto/storage/goog4_request",
2892+
)
2893+
self.assertEqual(fields["x-goog-signature"], EXPECTED_SIGN)
2894+
self.assertEqual(fields["policy"], EXPECTED_POLICY)
2895+
28522896

28532897
class Test__item_to_bucket(unittest.TestCase):
28542898
def _call_fut(self, iterator, item):

0 commit comments

Comments
 (0)