Class: FasterS3Url::Builder
- Inherits:
-
Object
- Object
- FasterS3Url::Builder
- Defined in:
- lib/faster_s3_url/builder.rb
Overview
Signing algorithm based on Amazon docs at docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html , as well as some interactive code reading of Aws::Sigv4::Signer github.com/aws/aws-sdk-ruby/blob/6114bc9692039ac75c8292c66472dacd14fa6f9a/gems/aws-sigv4/lib/aws-sigv4/signer.rb as used by Aws::S3::Presigner github.com/aws/aws-sdk-ruby/blob/6114bc9692039ac75c8292c66472dacd14fa6f9a/gems/aws-sdk-s3/lib/aws-sdk-s3/presigner.rb
Constant Summary collapse
- FIFTEEN_MINUTES =
60 * 15
- ONE_WEEK =
60 * 60 * 24 * 7
- SIGNED_HEADERS =
"host".freeze
- METHOD =
"GET".freeze
- ALGORITHM =
"AWS4-HMAC-SHA256".freeze
- SERVICE =
"s3".freeze
- DEFAULT_EXPIRES_IN =
15 minutes, seems to be AWS SDK default
FIFTEEN_MINUTES
- MAX_CACHED_SIGNING_KEYS =
5
Instance Attribute Summary collapse
-
#access_key_id ⇒ Object
readonly
Returns the value of attribute access_key_id.
-
#bucket_name ⇒ Object
readonly
Returns the value of attribute bucket_name.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#region ⇒ Object
readonly
Returns the value of attribute region.
-
#session_token ⇒ Object
readonly
Returns the value of attribute session_token.
Instance Method Summary collapse
-
#initialize(bucket_name:, region:, access_key_id:, secret_access_key:, session_token: nil, host: nil, endpoint: nil, default_public: true, cache_signing_keys: false) ⇒ Builder
constructor
A new instance of Builder.
-
#presigned_url(key, time: nil, expires_in: DEFAULT_EXPIRES_IN, response_cache_control: nil, response_content_disposition: nil, response_content_encoding: nil, response_content_language: nil, response_content_type: nil, response_expires: nil, version_id: nil) ⇒ Object
Generates a presigned GET URL for a specified S3 object key.
- #public_url(key) ⇒ Object
-
#url(key, public: @default_public, **options) ⇒ Object
just a convenience method that can call public_url or presigned_url based on flag.
Constructor Details
#initialize(bucket_name:, region:, access_key_id:, secret_access_key:, session_token: nil, host: nil, endpoint: nil, default_public: true, cache_signing_keys: false) ⇒ Builder
Returns a new instance of Builder.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/faster_s3_url/builder.rb', line 44 def initialize(bucket_name:, region:, access_key_id:, secret_access_key:, session_token:nil, host:nil, endpoint: nil, default_public: true, cache_signing_keys: false) if endpoint && host raise ArgumentError.new("`endpoint` and `host` are mutually exclusive, you can only provide one. You provided endpoint: #{endpoint.inspect} and host: #{host.inspect}") end @bucket_name = bucket_name @region = region parsed_uri = parsed_base_uri(bucket_name: bucket_name, host: host, endpoint: endpoint) @base_url = URI.join(parsed_uri, "/").to_s.chomp("/") # without path @base_path = parsed_uri.path # path component of base url, usually empty @host = parsed_uri.host @canonical_headers = "host:#{parsed_uri.port == parsed_uri.default_port ? @host : "#{parsed_uri.host}:#{parsed_uri.port}"}\n" @default_public = default_public @access_key_id = access_key_id @secret_access_key = secret_access_key @cache_signing_keys = cache_signing_keys @session_token = session_token if @cache_signing_keys @signing_key_cache = {} end end |
Instance Attribute Details
#access_key_id ⇒ Object (readonly)
Returns the value of attribute access_key_id.
23 24 25 |
# File 'lib/faster_s3_url/builder.rb', line 23 def access_key_id @access_key_id end |
#bucket_name ⇒ Object (readonly)
Returns the value of attribute bucket_name.
23 24 25 |
# File 'lib/faster_s3_url/builder.rb', line 23 def bucket_name @bucket_name end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
23 24 25 |
# File 'lib/faster_s3_url/builder.rb', line 23 def host @host end |
#region ⇒ Object (readonly)
Returns the value of attribute region.
23 24 25 |
# File 'lib/faster_s3_url/builder.rb', line 23 def region @region end |
#session_token ⇒ Object (readonly)
Returns the value of attribute session_token.
23 24 25 |
# File 'lib/faster_s3_url/builder.rb', line 23 def session_token @session_token end |
Instance Method Details
#presigned_url(key, time: nil, expires_in: DEFAULT_EXPIRES_IN, response_cache_control: nil, response_content_disposition: nil, response_content_encoding: nil, response_content_language: nil, response_content_type: nil, response_expires: nil, version_id: nil) ⇒ Object
Generates a presigned GET URL for a specified S3 object key.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/faster_s3_url/builder.rb', line 99 def presigned_url(key, time: nil, expires_in: DEFAULT_EXPIRES_IN, response_cache_control: nil, response_content_disposition: nil, response_content_encoding: nil, response_content_language: nil, response_content_type: nil, response_expires: nil, version_id: nil) validate_expires_in(expires_in) canonical_uri = self.base_path + "/" + uri_escape_key(key) now = time ? time.dup.utc : Time.now.utc # Uh Time#utc is mutating, not nice to do to an argument! amz_date = now.strftime("%Y%m%dT%H%M%SZ") datestamp = now.strftime("%Y%m%d") credential_scope = datestamp + '/' + region + '/' + SERVICE + '/' + 'aws4_request' # These have to be sorted, but sort is case-sensitive, and we have a fixed # list of headers we know might be here... turns out they are already sorted? canonical_query_params = { "X-Amz-Algorithm": ALGORITHM, "X-Amz-Credential": uri_escape(@access_key_id + "/" + credential_scope), "X-Amz-Date": amz_date, "X-Amz-Expires": expires_in.to_s, "X-Amz-Security-Token": uri_escape(session_token), "X-Amz-SignedHeaders": SIGNED_HEADERS, "response-cache-control": uri_escape(response_cache_control), "response-content-disposition": uri_escape(response_content_disposition), "response-content-encoding": uri_escape(response_content_encoding), "response-content-language": uri_escape(response_content_language), "response-content-type": uri_escape(response_content_type), "response-expires": uri_escape((response_expires)), "versionId": uri_escape(version_id) }.compact canonical_query_string = canonical_query_params.collect {|k, v| "#{k}=#{v}" }.join("&") canonical_request = ["GET", canonical_uri, canonical_query_string, @canonical_headers, SIGNED_HEADERS, 'UNSIGNED-PAYLOAD' ].join("\n") string_to_sign = [ ALGORITHM, amz_date, credential_scope, Digest::SHA256.hexdigest(canonical_request) ].join("\n") signing_key = retrieve_signing_key(datestamp) signature = OpenSSL::HMAC.hexdigest("SHA256", signing_key, string_to_sign) return "#{base_url}#{canonical_uri}?#{canonical_query_string}&X-Amz-Signature=#{signature}" end |
#public_url(key) ⇒ Object
68 69 70 |
# File 'lib/faster_s3_url/builder.rb', line 68 def public_url(key) "#{self.base_url}#{self.base_path}/#{uri_escape_key(key)}" end |
#url(key, public: @default_public, **options) ⇒ Object
just a convenience method that can call public_url or presigned_url based on flag
signer.url(object_key, public: true)
#=> forwards to signer.public_url(object_key)
signer.url(object_key, public: false, response_content_type: "image/jpeg")
#=> forwards to signer.presigned_url(object_key, response_content_type: "image/jpeg")
Options (sucn as response_content_type) that are not applicable to #public_url
are ignored in public mode.
The default value of `public` can be set by initializer arg `default_public`, which
is itself default true.
builder = FasterS3Url::Builder.new(..., default_public: false)
builder.url(object_key) # will call #presigned_url
174 175 176 177 178 179 180 |
# File 'lib/faster_s3_url/builder.rb', line 174 def url(key, public: @default_public, **) if public public_url(key) else presigned_url(key, **) end end |