|
19 | 19 | import datetime |
20 | 20 | import hashlib |
21 | 21 | import re |
| 22 | +import json |
22 | 23 |
|
23 | 24 | import six |
24 | 25 |
|
25 | 26 | import google.auth.credentials |
| 27 | + |
| 28 | +from google.auth import exceptions |
| 29 | +from google.auth.transport import requests |
26 | 30 | from google.cloud import _helpers |
27 | 31 |
|
28 | 32 |
|
@@ -265,6 +269,8 @@ def generate_signed_url_v2( |
265 | 269 | generation=None, |
266 | 270 | headers=None, |
267 | 271 | query_parameters=None, |
| 272 | + service_account_email=None, |
| 273 | + access_token=None, |
268 | 274 | ): |
269 | 275 | """Generate a V2 signed URL to provide query-string auth'n to a resource. |
270 | 276 |
|
@@ -340,6 +346,12 @@ def generate_signed_url_v2( |
340 | 346 | Requests using the signed URL *must* pass the specified header |
341 | 347 | (name and value) with each request for the URL. |
342 | 348 |
|
| 349 | + :type service_account_email: str |
| 350 | + :param service_account_email: (Optional) E-mail address of the service account. |
| 351 | +
|
| 352 | + :type access_token: str |
| 353 | + :param access_token: (Optional) Access token for a service account. |
| 354 | +
|
343 | 355 | :type query_parameters: dict |
344 | 356 | :param query_parameters: |
345 | 357 | (Optional) Additional query paramtersto be included as part of the |
@@ -370,9 +382,17 @@ def generate_signed_url_v2( |
370 | 382 | string_to_sign = "\n".join(elements_to_sign) |
371 | 383 |
|
372 | 384 | # Set the right query parameters. |
373 | | - signed_query_params = get_signed_query_params_v2( |
374 | | - credentials, expiration_stamp, string_to_sign |
375 | | - ) |
| 385 | + if access_token and service_account_email: |
| 386 | + signature = _sign_message(string_to_sign, access_token, service_account_email) |
| 387 | + signed_query_params = { |
| 388 | + "GoogleAccessId": service_account_email, |
| 389 | + "Expires": str(expiration), |
| 390 | + "Signature": signature, |
| 391 | + } |
| 392 | + else: |
| 393 | + signed_query_params = get_signed_query_params_v2( |
| 394 | + credentials, expiration_stamp, string_to_sign |
| 395 | + ) |
376 | 396 |
|
377 | 397 | if response_type is not None: |
378 | 398 | signed_query_params["response-content-type"] = response_type |
@@ -409,6 +429,8 @@ def generate_signed_url_v4( |
409 | 429 | generation=None, |
410 | 430 | headers=None, |
411 | 431 | query_parameters=None, |
| 432 | + service_account_email=None, |
| 433 | + access_token=None, |
412 | 434 | _request_timestamp=None, # for testing only |
413 | 435 | ): |
414 | 436 | """Generate a V4 signed URL to provide query-string auth'n to a resource. |
@@ -492,6 +514,12 @@ def generate_signed_url_v4( |
492 | 514 | signed URLs. See: |
493 | 515 | https://cloud.google.com/storage/docs/xml-api/reference-headers#query |
494 | 516 |
|
| 517 | + :type service_account_email: str |
| 518 | + :param service_account_email: (Optional) E-mail address of the service account. |
| 519 | +
|
| 520 | + :type access_token: str |
| 521 | + :param access_token: (Optional) Access token for a service account. |
| 522 | +
|
495 | 523 | :raises: :exc:`TypeError` when expiration is not a valid type. |
496 | 524 | :raises: :exc:`AttributeError` if credentials is not an instance |
497 | 525 | of :class:`google.auth.credentials.Signing`. |
@@ -583,9 +611,58 @@ def generate_signed_url_v4( |
583 | 611 | ] |
584 | 612 | string_to_sign = "\n".join(string_elements) |
585 | 613 |
|
586 | | - signature_bytes = credentials.sign_bytes(string_to_sign.encode("ascii")) |
587 | | - signature = binascii.hexlify(signature_bytes).decode("ascii") |
| 614 | + if access_token and service_account_email: |
| 615 | + signature = _sign_message(string_to_sign, access_token, service_account_email) |
| 616 | + signature_bytes = base64.b64decode(signature) |
| 617 | + signature = binascii.hexlify(signature_bytes).decode("ascii") |
| 618 | + else: |
| 619 | + signature_bytes = credentials.sign_bytes(string_to_sign.encode("ascii")) |
| 620 | + signature = binascii.hexlify(signature_bytes).decode("ascii") |
588 | 621 |
|
589 | 622 | return "{}{}?{}&X-Goog-Signature={}".format( |
590 | 623 | api_access_endpoint, resource, canonical_query_string, signature |
591 | 624 | ) |
| 625 | + |
| 626 | + |
| 627 | +def _sign_message(message, access_token, service_account_email): |
| 628 | + |
| 629 | + """Signs a message. |
| 630 | +
|
| 631 | + :type message: str |
| 632 | + :param message: The message to be signed. |
| 633 | +
|
| 634 | + :type access_token: str |
| 635 | + :param access_token: Access token for a service account. |
| 636 | +
|
| 637 | +
|
| 638 | + :type service_account_email: str |
| 639 | + :param service_account_email: E-mail address of the service account. |
| 640 | +
|
| 641 | + :raises: :exc:`TransportError` if an `access_token` is unauthorized. |
| 642 | +
|
| 643 | + :rtype: str |
| 644 | + :returns: The signature of the message. |
| 645 | +
|
| 646 | + """ |
| 647 | + message = _helpers._to_bytes(message) |
| 648 | + |
| 649 | + method = "POST" |
| 650 | + url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/{}:signBlob?alt=json".format( |
| 651 | + service_account_email |
| 652 | + ) |
| 653 | + headers = { |
| 654 | + "Authorization": "Bearer " + access_token, |
| 655 | + "Content-type": "application/json", |
| 656 | + } |
| 657 | + body = json.dumps({"bytesToSign": base64.b64encode(message).decode("utf-8")}) |
| 658 | + |
| 659 | + request = requests.Request() |
| 660 | + response = request(url=url, method=method, body=body, headers=headers) |
| 661 | + |
| 662 | + if response.status != six.moves.http_client.OK: |
| 663 | + raise exceptions.TransportError( |
| 664 | + "Error calling the IAM signBytes API: {}".format(response.data) |
| 665 | + ) |
| 666 | + |
| 667 | + data = json.loads(response.data.decode("utf-8")) |
| 668 | + return data["signature"] |
0 commit comments