class Aws::S3::Presigner

Constants

BLACKLISTED_HEADERS

@api private

FIFTEEN_MINUTES

@api private

ONE_WEEK

@api private

Public Class Methods

new(options = {}) click to toggle source

@option options [Client] :client Optionally provide an existing

S3 client
# File lib/aws-sdk-s3/presigner.rb, line 35
def initialize(options = {})
  @client = options[:client] || Aws::S3::Client.new
end

Public Instance Methods

presigned_request(method, params = {}) click to toggle source

Allows you to create presigned URL requests for S3 operations. This method returns a tuple containing the URL and the signed X-amz-* headers to be used with the presigned url.

@example

signer = Aws::S3::Presigner.new
url, headers = signer.presigned_request(
  :get_object, bucket: "bucket", key: "key"
)

@param [Symbol] method Symbolized method name of the operation you want

to presign.

@option params [Integer] :expires_in (900) The number of seconds

before the presigned URL expires. Defaults to 15 minutes. As signature
version 4 has a maximum expiry time of one week for presigned URLs,
attempts to set this value to greater than one week (604800) will
raise an exception.

@option params [Time] :time (Time.now) The starting time for when the

presigned url becomes active.

@option params [Boolean] :secure (true) When `false`, a HTTP URL

is returned instead of the default HTTPS URL.

@option params [Boolean] :virtual_host (false) When `true`, the

bucket name will be used as the hostname. This will cause
the returned URL to be 'http' and not 'https'.

@option params [Boolean] :use_accelerate_endpoint (false) When `true`,

Presigner will attempt to use accelerated endpoint.

@option params [Array<String>] :whitelist_headers ([]) Additional

headers to be included for the signed request. Certain headers beyond
the authorization header could, in theory, be changed for various
reasons (including but not limited to proxies) while in transit and
after signing. This would lead to signature errors being returned,
despite no actual problems with signing. (see BLACKLISTED_HEADERS)

@raise [ArgumentError] Raises an ArgumentError if `:expires_in`

exceeds one week.

@return [String, Hash] A tuple with a presigned URL and headers that

should be included with the request.
# File lib/aws-sdk-s3/presigner.rb, line 126
def presigned_request(method, params = {})
  _presigned_request(method, params, false)
end
presigned_url(method, params = {}) click to toggle source

Create presigned URLs for S3 operations.

@example

signer = Aws::S3::Presigner.new
url = signer.presigned_url(:get_object, bucket: "bucket", key: "key")

@param [Symbol] method Symbolized method name of the operation you want

to presign.

@option params [Integer] :expires_in (900) The number of seconds

before the presigned URL expires. Defaults to 15 minutes. As signature
version 4 has a maximum expiry time of one week for presigned URLs,
attempts to set this value to greater than one week (604800) will
raise an exception.

@option params [Time] :time (Time.now) The starting time for when the

presigned url becomes active.

@option params [Boolean] :secure (true) When `false`, a HTTP URL

is returned instead of the default HTTPS URL.

@option params [Boolean] :virtual_host (false) When `true`, the

bucket name will be used as the hostname.

@option params [Boolean] :use_accelerate_endpoint (false) When `true`,

Presigner will attempt to use accelerated endpoint.

@option params [Array<String>] :whitelist_headers ([]) Additional

headers to be included for the signed request. Certain headers beyond
the authorization header could, in theory, be changed for various
reasons (including but not limited to proxies) while in transit and
after signing. This would lead to signature errors being returned,
despite no actual problems with signing. (see BLACKLISTED_HEADERS)

@raise [ArgumentError] Raises an ArgumentError if `:expires_in`

exceeds one week.

@return [String] a presigned url

# File lib/aws-sdk-s3/presigner.rb, line 77
def presigned_url(method, params = {})
  url, _headers = _presigned_request(method, params)
  url
end

Private Instance Methods

_presigned_request(method, params, hoist = true) click to toggle source
# File lib/aws-sdk-s3/presigner.rb, line 132
def _presigned_request(method, params, hoist = true)
  virtual_host = params.delete(:virtual_host)
  time = params.delete(:time)
  unsigned_headers = unsigned_headers(params)
  scheme = http_scheme(params)
  expires_in = expires_in(params)

  req = @client.build_request(method, params)
  use_bucket_as_hostname(req) if virtual_host
  handle_presigned_url_context(req)

  x_amz_headers = sign_but_dont_send(
    req, expires_in, scheme, time, unsigned_headers, hoist
  )
  [req.send_request.data, x_amz_headers]
end
expires_in(params) click to toggle source
# File lib/aws-sdk-s3/presigner.rb, line 162
def expires_in(params)
  if (expires_in = params.delete(:expires_in))
    if expires_in > ONE_WEEK
      raise ArgumentError,
            "expires_in value of #{expires_in} exceeds one-week maximum."
    elsif expires_in <= 0
      raise ArgumentError,
            "expires_in value of #{expires_in} cannot be 0 or less."
    end
    expires_in
  else
    FIFTEEN_MINUTES
  end
end
handle_presigned_url_context(req) click to toggle source

Used for excluding presigned_urls from API request count.

Store context information as early as possible, to allow handlers to perform decisions based on this flag if need.

# File lib/aws-sdk-s3/presigner.rb, line 191
def handle_presigned_url_context(req)
  req.handle(step: :initialize, priority: 98) do |context|
    context[:presigned_url] = true
    @handler.call(context)
  end
end
http_scheme(params) click to toggle source
# File lib/aws-sdk-s3/presigner.rb, line 154
def http_scheme(params)
  if params.delete(:secure) == false
    'http'
  else
    @client.config.endpoint.scheme
  end
end
sign_but_dont_send( req, expires_in, scheme, time, unsigned_headers, hoist = true ) click to toggle source

@param [Seahorse::Client::Request] req

# File lib/aws-sdk-s3/presigner.rb, line 199
def sign_but_dont_send(
  req, expires_in, scheme, time, unsigned_headers, hoist = true
)
  x_amz_headers = {}

  http_req = req.context.http_request

  req.handlers.remove(Aws::S3::Plugins::S3Signer::LegacyHandler)
  req.handlers.remove(Aws::S3::Plugins::S3Signer::V4Handler)
  req.handlers.remove(Seahorse::Client::Plugins::ContentLength::Handler)

  req.handle(step: :send) do |context|
    if scheme != http_req.endpoint.scheme
      endpoint = http_req.endpoint.dup
      endpoint.scheme = scheme
      endpoint.port = (scheme == 'http' ? 80 : 443)
      http_req.endpoint = URI.parse(endpoint.to_s)
    end

    query = http_req.endpoint.query ? http_req.endpoint.query.split('&') : []
    http_req.headers.each do |key, value|
      next unless key =~ /^x-amz/i

      if hoist
        value = Aws::Sigv4::Signer.uri_escape(value)
        key = Aws::Sigv4::Signer.uri_escape(key)
        # hoist x-amz-* headers to the querystring
        http_req.headers.delete(key)
        query << "#{key}=#{value}"
      else
        x_amz_headers[key] = value
      end
    end
    http_req.endpoint.query = query.join('&') unless query.empty?

    signing_algorithm = :sigv4

    # If it's an ARN, get the resolved region and service
    if (arn = context.metadata[:s3_arn])
      region = arn[:resolved_region]
      service = arn[:arn].service
      region = arn[:arn].is_a?(MultiRegionAccessPointARN) ? '*': arn[:resolved_region]
      signing_algorithm = arn[:arn].is_a?(MultiRegionAccessPointARN) ? :sigv4a : :sigv4
    end

    signer = Aws::Sigv4::Signer.new(
      service: service || 's3',
      region: region || context.config.region,
      signing_algorithm: signing_algorithm,
      credentials_provider: context.config.credentials,
      unsigned_headers: unsigned_headers,
      apply_checksum_header: false,
      uri_escape_path: false
    )

    url = signer.presign_url(
      http_method: http_req.http_method,
      url: http_req.endpoint,
      headers: http_req.headers,
      body_digest: 'UNSIGNED-PAYLOAD',
      expires_in: expires_in,
      time: time
    ).to_s

    Seahorse::Client::Response.new(context: context, data: url)
  end
  # Return the headers
  x_amz_headers
end
unsigned_headers(params) click to toggle source
# File lib/aws-sdk-s3/presigner.rb, line 149
def unsigned_headers(params)
  whitelist_headers = params.delete(:whitelist_headers) || []
  BLACKLISTED_HEADERS - whitelist_headers
end
use_bucket_as_hostname(req) click to toggle source
# File lib/aws-sdk-s3/presigner.rb, line 177
def use_bucket_as_hostname(req)
  req.handlers.remove(Plugins::BucketDns::Handler)
  req.handle do |context|
    uri = context.http_request.endpoint
    uri.host = context.params[:bucket]
    uri.path.sub!("/#{context.params[:bucket]}", '')
    @handler.call(context)
  end
end