.. Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
.. SPDX-License-Identifier: Apache-2.0

##############
Presigned URLs
##############

A user who does not have AWS credentials or permission to access an S3 object 
can be granted temporary access by using a presigned URL.

A presigned URL is generated by an AWS user who has access to the object. The 
generated URL is then given to the unauthorized user. The presigned URL can be 
entered in a browser or used by a program or HTML webpage. The credentials 
used by the presigned URL are those of the AWS user who generated the URL.

A presigned URL remains valid for a limited period of time which is specified 
when the URL is generated.

It is recommended to configure the S3 client with Signature Version 4 
(``s3v4``), the AWS region of the bucket and to use virtual-hosted style addressing ``s3={'addressing_style': 'virtual'}`` when generating presigned URLs.

.. code-block:: python

    import logging
    import boto3
    from botocore.exceptions import ClientError
    from botocore.config import Config


    def create_presigned_url(
        bucket_name, object_name, region_name, expiration=3600
    ):
        """Generate a presigned URL to share an S3 object

        :param bucket_name: string
        :param object_name: string
        :param region_name: string
        :param expiration: Time in seconds for the presigned URL to remain valid
        :return: Presigned URL as string. If error, returns None.
        """

        # Generate a presigned URL for the S3 object
        s3_client = boto3.client(
            's3',
            region_name=region_name,
            config=Config(
                signature_version='s3v4',
                s3={'addressing_style': 'virtual'},
            ),
        )
        try:
            response = s3_client.generate_presigned_url(
                'get_object',
                Params={'Bucket': bucket_name, 'Key': object_name},
                ExpiresIn=expiration,
            )
        except ClientError as e:
            logging.error(e)
            return None

        # The response contains the presigned URL
        return response


The user can download the S3 object by entering the presigned URL in a browser. 
A program or HTML page can download the S3 object by using the presigned URL 
as part of an HTTP GET request.

The following code demonstrates using the Python ``requests`` package to 
perform a GET request.

.. code-block:: python

    import requests  # To install: pip install requests

    url = create_presigned_url('amzn-s3-demo-bucket', 'OBJECT_NAME', 'us-east-1')
    if url is not None:
        response = requests.get(url)


Using presigned URLs to perform other S3 operations
===================================================

The main purpose of presigned URLs is to grant a user temporary access to an 
S3 object. However, presigned URLs can be used to grant permission to perform 
additional operations on S3 buckets and objects.

The ``create_presigned_url_expanded`` method shown below generates a presigned 
URL to perform a specified S3 operation. The method accepts the name of the S3 
``Client`` method to perform, such as 'list_buckets' or 'get_bucket_location.' 
The parameters to pass to the method are specified in the 
``method_parameters`` dictionary argument. The HTTP method to use (GET, PUT, 
etc.) can be specified, but the AWS SDK for Python will automatically select 
the appropriate method so this argument is not normally required.

.. code-block:: python

    import logging
    import boto3
    from botocore.exceptions import ClientError
    from botocore.config import Config


    def create_presigned_url_expanded(
        client_method_name,
        region_name,
        method_parameters=None,
        expiration=3600,
        http_method=None,
    ):
        """Generate a presigned URL to invoke an S3.Client method

        Not all the client methods provided in the AWS Python SDK are supported.

        :param client_method_name: Name of the S3.Client method, e.g., 'list_buckets'
        :param region_name: Name of the region the s3 client will use for signing, e.g., 'us-west-2'
        :param method_parameters: Dictionary of parameters to send to the method
        :param expiration: Time in seconds for the presigned URL to remain valid
        :param http_method: HTTP method to use (GET, etc.)
        :return: Presigned URL as string. If error, returns None.
        """

        # Generate a presigned URL for the S3 client method
        s3_client = boto3.client(
            's3',
            region_name=region_name,
            config=Config(
                signature_version='s3v4',
                s3={'addressing_style': 'virtual'},
            ),
        )
        try:
            response = s3_client.generate_presigned_url(
                ClientMethod=client_method_name,
                Params=method_parameters,
                ExpiresIn=expiration,
                HttpMethod=http_method,
            )
        except ClientError as e:
            logging.error(e)
            return None

        # The response contains the presigned URL
        return response


Generating a presigned URL to upload a file
===========================================

A user who does not have AWS credentials to upload a file can use a 
presigned URL to perform the upload. The upload operation makes an HTTP POST 
request and requires additional parameters to be sent as part of the request.

.. code-block:: python

    import logging
    import boto3
    from botocore.exceptions import ClientError
    from botocore.config import Config


    def create_presigned_post(
        bucket_name,
        object_name,
        region_name,
        fields=None,
        conditions=None,
        expiration=3600,
    ):
        """Generate a presigned URL S3 POST request to upload a file

        :param bucket_name: string
        :param object_name: string
        :param region_name: string
        :param fields: Dictionary of prefilled form fields
        :param conditions: List of conditions to include in the policy
        :param expiration: Time in seconds for the presigned URL to remain valid
        :return: Dictionary with the following keys:
            url: URL to post to
            fields: Dictionary of form fields and values to submit with the POST
        :return: None if error.
        """

        # Generate a presigned S3 POST URL
        s3_client = boto3.client(
            's3',
            region_name=region_name,
            config=Config(
                signature_version='s3v4',
                s3={'addressing_style': 'virtual'},
            ),
        )
        try:
            response = s3_client.generate_presigned_post(
                bucket_name,
                object_name,
                Fields=fields,
                Conditions=conditions,
                ExpiresIn=expiration,
            )
        except ClientError as e:
            logging.error(e)
            return None

        # The response contains the presigned URL and required fields
        return response

The generated presigned URL includes both a URL and additional fields that 
must be passed as part of the subsequent HTTP POST request.

The following code demonstrates how to use the ``requests`` package with a 
presigned POST URL to perform a POST request to upload a file to S3.

.. code-block:: python

    import requests  # To install: pip install requests

    # Generate a presigned S3 POST URL
    object_name = 'OBJECT_NAME'
    response = create_presigned_post('amzn-s3-demo-bucket', object_name, 'us-east-1')
    if response is None:
        exit(1)

    # Demonstrate how another Python program can use the presigned URL to upload a file
    with open(object_name, 'rb') as f:
        files = {'file': (object_name, f)}
        http_response = requests.post(response['url'], data=response['fields'], files=files)
    # If successful, returns HTTP status code 204
    logging.info(f'File upload HTTP status code: {http_response.status_code}')

The presigned POST URL and fields values can also be used in an HTML page.

.. code-block:: html

    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      </head>
      <body>
        <!-- Copy the 'url' value returned by S3Client.generate_presigned_post() -->
        <form action="URL_VALUE" method="post" enctype="multipart/form-data">
          <!-- Copy the 'fields' key:values returned by S3Client.generate_presigned_post() -->
          <input type="hidden" name="key" value="VALUE" />
          <input type="hidden" name="X-Amz-Credential" value="VALUE" />
          <input type="hidden" name="X-Amz-Algorithm" value="VALUE" />
          <input type="hidden" name="X-Amz-Date" value="VALUE" />
          <input type="hidden" name="Policy" value="VALUE" />
          <input type="hidden" name="X-Amz-Signature" value="VALUE" />
        File:
          <input type="file"   name="file" /> <br />
          <input type="submit" name="submit" value="Upload to Amazon S3" />
        </form>
      </body>
    </html>
