def get_signed_query_params(credentials, expiration, string_to_sign): """Gets query parameters for creating a signed URL. :type credentials: :class:`google.auth.credentials.Signer` :param credentials: The credentials used to create a private key for signing text. :type expiration: int or long :param expiration: When the signed URL should expire. :type string_to_sign: str :param string_to_sign: The string to be signed by the credentials. :raises AttributeError: If :meth: sign_blob is unavailable. :rtype: dict :returns: Query parameters matching the signing credentials with a signed payload. """ ensure_signed_credentials(credentials) signature_bytes = credentials.sign_bytes(string_to_sign) signature = base64.b64encode(signature_bytes) service_account_name = credentials.signer_email return { "GoogleAccessId": service_account_name, "Expires": str(expiration), "Signature": signature, }
def get_signed_query_params_v2(credentials, expiration, string_to_sign): """Gets query parameters for creating a signed URL. :type credentials: :class:`google.auth.credentials.Signing` :param credentials: The credentials used to create a private key for signing text. :type expiration: int or long :param expiration: When the signed URL should expire. :type string_to_sign: str :param string_to_sign: The string to be signed by the credentials. :raises: :exc:`AttributeError` if credentials is not an instance of :class:`google.auth.credentials.Signing`. :rtype: dict :returns: Query parameters matching the signing credentials with a signed payload. """ ensure_signed_credentials(credentials) signature_bytes = credentials.sign_bytes(string_to_sign.encode("ascii")) signature = base64.b64encode(signature_bytes) service_account_name = credentials.signer_email return { "GoogleAccessId": service_account_name, "Expires": expiration, "Signature": signature, }
def get_signed_query_params(credentials, expiration, string_to_sign): """Gets query parameters for creating a signed URL. :type credentials: :class:`google.auth.credentials.Signer` :param credentials: The credentials used to create a private key for signing text. :type expiration: int or long :param expiration: When the signed URL should expire. :type string_to_sign: str :param string_to_sign: The string to be signed by the credentials. :raises AttributeError: If :meth: sign_blob is unavailable. :rtype: dict :returns: Query parameters matching the signing credentials with a signed payload. """ ensure_signed_credentials(credentials) signature_bytes = credentials.sign_bytes(string_to_sign) signature = base64.b64encode(signature_bytes) service_account_name = credentials.signer_email return { 'GoogleAccessId': service_account_name, 'Expires': str(expiration), 'Signature': signature, }
def _get_signed_query_params(credentials, expiration, string_to_sign): """Gets query parameters for creating a signed URL. :type credentials: :class:`google.auth.credentials.Signer` :param credentials: The credentials used to create a private key for signing text. :type expiration: int or long :param expiration: When the signed URL should expire. :type string_to_sign: str :param string_to_sign: The string to be signed by the credentials. :raises AttributeError: If :meth: sign_blob is unavailable. :rtype: dict :returns: Query parameters matching the signing credentials with a signed payload. """ if not isinstance(credentials, google.auth.credentials.Signing): auth_uri = ('http://google-cloud-python.readthedocs.io/en/latest/' 'google-cloud-auth.html#setting-up-a-service-account') raise AttributeError('you need a private key to sign credentials.' 'the credentials you are currently using %s ' 'just contains a token. see %s for more ' 'details.' % (type(credentials), auth_uri)) signature_bytes = credentials.sign_bytes(string_to_sign) signature = base64.b64encode(signature_bytes) service_account_name = credentials.signer_email return { 'GoogleAccessId': service_account_name, 'Expires': str(expiration), 'Signature': signature, }
def build_token(credentials, params, duration_minutes): issuer = credentials.signer_email issued_at = int(time.time()) data = { "iss": issuer, "sub": issuer, "aud": IDENTITY_ENDPOINT, "iat": issued_at, "exp": issued_at + duration_minutes * 60, } data.update(params) payload = TOKEN_HEADER + "." + encode(data) signature = credentials.sign_bytes(payload) return payload + "." + b64encode(signature)
def _decode_token(credentials, token, verify): try: header, data, signature = map(str, token.split(".")) except ValueError: raise ValueError("Invalid token data.") if header != TOKEN_HEADER: raise ValueError("Invalid token header.") if verify: payload = header + "." + data given_signature = b64decode(signature) expected_signature = credentials.sign_bytes(payload) if not hmac.compare_digest(given_signature, expected_signature): raise ValueError("Invalid token signature.") return decode(data)
def generate_signed_url_v4( credentials, resource, expiration, api_access_endpoint=DEFAULT_ENDPOINT, method="GET", content_md5=None, content_type=None, response_type=None, response_disposition=None, generation=None, headers=None, query_parameters=None, service_account_email=None, access_token=None, _request_timestamp=None, # for testing only ): """Generate a V4 signed URL to provide query-string auth'n to a resource. .. note:: Assumes ``credentials`` implements the :class:`google.auth.credentials.Signing` interface. Also assumes ``credentials`` has a ``service_account_email`` property which identifies the credentials. .. note:: 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/\ google-cloud-python/issues/922 .. _reference: https://cloud.google.com/storage/docs/reference-headers :type credentials: :class:`google.auth.credentials.Signing` :param credentials: Credentials object with an associated private key to sign text. That credentials must provide signer_email only if service_account_email and access_token are not passed. :type resource: str :param resource: A pointer to a specific resource (typically, ``/bucket-name/path/to/blob.txt``). Caller should have already URL-encoded the value. :type expiration: Union[Integer, datetime.datetime, datetime.timedelta] :param expiration: Point in time when the signed URL should expire. If a ``datetime`` instance is passed without an explicit ``tzinfo`` set, it will be assumed to be ``UTC``. :type api_access_endpoint: str :param api_access_endpoint: (Optional) URI base. Defaults to "https://storage.googleapis.com/" :type method: str :param method: The HTTP verb that will be used when requesting the URL. Defaults to ``'GET'``. If method is ``'RESUMABLE'`` then the signature will additionally contain the `x-goog-resumable` header, and the method changed to POST. See the signed URL docs regarding this flow: https://cloud.google.com/storage/docs/access-control/signed-urls :type content_md5: str :param content_md5: (Optional) The MD5 hash 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. Ignored if content_type is set on object/blob metadata. :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. :type headers: dict :param headers: (Optional) Additional HTTP headers to be included as part of the signed URLs. See: https://cloud.google.com/storage/docs/xml-api/reference-headers Requests using the signed URL *must* pass the specified header (name and value) with each request for the URL. :type query_parameters: dict :param query_parameters: (Optional) Additional query parameters to be included as part of the signed URLs. See: https://cloud.google.com/storage/docs/xml-api/reference-headers#query :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. :raises: :exc:`TypeError` when expiration is not a valid type. :raises: :exc:`AttributeError` if credentials is not an instance of :class:`google.auth.credentials.Signing`. :rtype: str :returns: A signed URL you can use to access the resource until expiration. """ expiration_seconds = get_expiration_seconds_v4(expiration) if _request_timestamp is None: request_timestamp, datestamp = get_v4_now_dtstamps() else: request_timestamp = _request_timestamp datestamp = _request_timestamp[:8] client_email = service_account_email if not access_token or not service_account_email: ensure_signed_credentials(credentials) client_email = credentials.signer_email credential_scope = "{}/auto/storage/goog4_request".format(datestamp) credential = "{}/{}".format(client_email, credential_scope) if headers is None: headers = {} if content_type is not None: headers["Content-Type"] = content_type if content_md5 is not None: headers["Content-MD5"] = content_md5 header_names = [key.lower() for key in headers] if "host" not in header_names: headers["Host"] = six.moves.urllib.parse.urlparse( api_access_endpoint).netloc if method.upper() == "RESUMABLE": method = "POST" headers["x-goog-resumable"] = "start" canonical_headers, ordered_headers = get_canonical_headers(headers) canonical_header_string = ( "\n".join(canonical_headers) + "\n" ) # Yes, Virginia, the extra newline is part of the spec. signed_headers = ";".join([key for key, _ in ordered_headers]) if query_parameters is None: query_parameters = {} else: query_parameters = { key: value or "" for key, value in query_parameters.items() } query_parameters["X-Goog-Algorithm"] = "GOOG4-RSA-SHA256" query_parameters["X-Goog-Credential"] = credential query_parameters["X-Goog-Date"] = request_timestamp query_parameters["X-Goog-Expires"] = expiration_seconds query_parameters["X-Goog-SignedHeaders"] = signed_headers if response_type is not None: query_parameters["response-content-type"] = response_type if response_disposition is not None: query_parameters["response-content-disposition"] = response_disposition if generation is not None: query_parameters["generation"] = generation canonical_query_string = _url_encode(query_parameters) lowercased_headers = dict(ordered_headers) if "x-goog-content-sha256" in lowercased_headers: payload = lowercased_headers["x-goog-content-sha256"] else: payload = "UNSIGNED-PAYLOAD" canonical_elements = [ method, resource, canonical_query_string, canonical_header_string, signed_headers, payload, ] canonical_request = "\n".join(canonical_elements) canonical_request_hash = hashlib.sha256( canonical_request.encode("ascii")).hexdigest() string_elements = [ "GOOG4-RSA-SHA256", request_timestamp, credential_scope, canonical_request_hash, ] string_to_sign = "\n".join(string_elements) if access_token and service_account_email: signature = _sign_message(string_to_sign, access_token, service_account_email) signature_bytes = base64.b64decode(signature) signature = binascii.hexlify(signature_bytes).decode("ascii") else: signature_bytes = credentials.sign_bytes( string_to_sign.encode("ascii")) signature = binascii.hexlify(signature_bytes).decode("ascii") return "{}{}?{}&X-Goog-Signature={}".format(api_access_endpoint, resource, canonical_query_string, signature)
def generate_upload_policy(self, conditions, expiration=None, client=None): """Create a signed upload policy for uploading objects. This method generates and signs a policy document. You can use `policy documents`_ to allow visitors to a website to upload files to Google Cloud Storage without giving them direct write access. For example: .. literalinclude:: storage_snippets.py :start-after: [START policy_document] :end-before: [END policy_document] .. _policy documents: https://cloud.google.com/storage/docs/xml-api\ /post-object#policydocument :type expiration: datetime :param expiration: Optional expiration in UTC. If not specified, the policy will expire in 1 hour. :type conditions: list :param conditions: A list of conditions as described in the `policy documents`_ documentation. :type client: :class:`~google.cloud.storage.client.Client` :param client: Optional. The client to use. If not passed, falls back to the ``client`` stored on the current bucket. :rtype: dict :returns: A dictionary of (form field name, form field value) of form fields that should be added to your HTML upload form in order to attach the signature. """ client = self._require_client(client) credentials = client._base_connection.credentials if not isinstance(credentials, google.auth.credentials.Signing): auth_uri = ('http://google-cloud-python.readthedocs.io/en/latest/' 'google-cloud-auth.html#setting-up-a-service-account') raise AttributeError('you need a private key to sign credentials.' 'the credentials you are currently using %s ' 'just contains a token. see %s for more ' 'details.' % (type(credentials), auth_uri)) if expiration is None: expiration = _NOW() + datetime.timedelta(hours=1) conditions = conditions + [ { 'bucket': self.name }, ] policy_document = { 'expiration': _datetime_to_rfc3339(expiration), 'conditions': conditions, } encoded_policy_document = base64.b64encode( json.dumps(policy_document).encode('utf-8')) signature = base64.b64encode( credentials.sign_bytes(encoded_policy_document)) fields = { 'bucket': self.name, 'GoogleAccessId': credentials.signer_email, 'policy': encoded_policy_document.decode('utf-8'), 'signature': signature.decode('utf-8'), } return fields
def generate_signed_url_v4( credentials, resource, expiration, api_access_endpoint=DEFAULT_ENDPOINT, method="GET", content_md5=None, content_type=None, response_type=None, response_disposition=None, generation=None, headers=None, query_parameters=None, _request_timestamp=None, # for testing only ): """Generate a V4 signed URL to provide query-string auth'n to a resource. .. note:: Assumes ``credentials`` implements the :class:`google.auth.credentials.Signing` interface. Also assumes ``credentials`` has a ``service_account_email`` property which identifies the credentials. .. note:: 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/\ google-cloud-python/issues/922 .. _reference: https://cloud.google.com/storage/docs/reference-headers :type credentials: :class:`google.auth.credentials.Signing` :param credentials: Credentials object with an associated private key to sign text. :type resource: str :param resource: A pointer to a specific resource (typically, ``/bucket-name/path/to/blob.txt``). :type expiration: Union[Integer, datetime.datetime, datetime.timedelta] :param expiration: Point in time when the signed URL should expire. :type api_access_endpoint: str :param api_access_endpoint: Optional URI base. Defaults to "https://storage.googleapis.com/" :type method: str :param method: The HTTP verb that will be used when requesting the URL. Defaults to ``'GET'``. If method is ``'RESUMABLE'`` then the signature will additionally contain the `x-goog-resumable` header, and the method changed to POST. See the signed URL docs regarding this flow: https://cloud.google.com/storage/docs/access-control/signed-urls :type content_md5: str :param content_md5: (Optional) The MD5 hash 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. :type headers: dict :param headers: (Optional) Additional HTTP headers to be included as part of the signed URLs. See: https://cloud.google.com/storage/docs/xml-api/reference-headers Requests using the signed URL *must* pass the specified header (name and value) with each request for the URL. :type query_parameters: dict :param query_parameters: (Optional) Additional query paramtersto be included as part of the signed URLs. See: https://cloud.google.com/storage/docs/xml-api/reference-headers#query :raises: :exc:`TypeError` when expiration is not a valid type. :raises: :exc:`AttributeError` if credentials is not an instance of :class:`google.auth.credentials.Signing`. :rtype: str :returns: A signed URL you can use to access the resource until expiration. """ ensure_signed_credentials(credentials) expiration_seconds = get_expiration_seconds_v4(expiration) if _request_timestamp is None: now = NOW() request_timestamp = now.strftime("%Y%m%dT%H%M%SZ") datestamp = now.date().strftime("%Y%m%d") else: request_timestamp = _request_timestamp datestamp = _request_timestamp[:8] client_email = credentials.signer_email credential_scope = "{}/auto/storage/goog4_request".format(datestamp) credential = "{}/{}".format(client_email, credential_scope) if headers is None: headers = {} if content_type is not None: headers["Content-Type"] = content_type if content_md5 is not None: headers["Content-MD5"] = content_md5 header_names = [key.lower() for key in headers] if "host" not in header_names: headers["Host"] = "storage.googleapis.com" if method.upper() == "RESUMABLE": method = "POST" headers["x-goog-resumable"] = "start" canonical_headers, ordered_headers = get_canonical_headers(headers) canonical_header_string = ( "\n".join(canonical_headers) + "\n" ) # Yes, Virginia, the extra newline is part of the spec. signed_headers = ";".join([key for key, _ in ordered_headers]) if query_parameters is None: query_parameters = {} else: query_parameters = {key: value or "" for key, value in query_parameters.items()} query_parameters["X-Goog-Algorithm"] = "GOOG4-RSA-SHA256" query_parameters["X-Goog-Credential"] = credential query_parameters["X-Goog-Date"] = request_timestamp query_parameters["X-Goog-Expires"] = expiration_seconds query_parameters["X-Goog-SignedHeaders"] = signed_headers if response_type is not None: query_parameters["response-content-type"] = response_type if response_disposition is not None: query_parameters["response-content-disposition"] = response_disposition if generation is not None: query_parameters["generation"] = generation ordered_query_parameters = sorted(query_parameters.items()) canonical_query_string = six.moves.urllib.parse.urlencode(ordered_query_parameters) canonical_elements = [ method, resource, canonical_query_string, canonical_header_string, signed_headers, "UNSIGNED-PAYLOAD", ] canonical_request = "\n".join(canonical_elements) canonical_request_hash = hashlib.sha256( canonical_request.encode("ascii") ).hexdigest() string_elements = [ "GOOG4-RSA-SHA256", request_timestamp, credential_scope, canonical_request_hash, ] string_to_sign = "\n".join(string_elements) signature_bytes = credentials.sign_bytes(string_to_sign.encode("ascii")) signature = binascii.hexlify(signature_bytes).decode("ascii") return "{}{}?{}&X-Goog-Signature={}".format( api_access_endpoint, resource, canonical_query_string, signature )