awsraw 0.1.9 → 1.0.0.alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +121 -49
- data/Rakefile +0 -6
- data/awsraw.gemspec +6 -5
- data/lib/awsraw.rb +8 -3
- data/lib/awsraw/credentials.rb +7 -0
- data/lib/awsraw/error.rb +4 -0
- data/lib/awsraw/s3/canonicalized_resource.rb +50 -0
- data/lib/awsraw/s3/client.rb +50 -28
- data/lib/awsraw/s3/content_md5_header.rb +34 -0
- data/lib/awsraw/s3/faraday_middleware.rb +39 -0
- data/lib/awsraw/s3/query_string_signer.rb +26 -37
- data/lib/awsraw/s3/signature.rb +52 -0
- data/lib/awsraw/s3/string_to_sign.rb +49 -0
- data/lib/awsraw/version.rb +2 -2
- data/spec/s3/canonicalized_resource_spec.rb +51 -0
- data/spec/s3/content_md5_header_spec.rb +27 -0
- data/spec/s3/faraday_middleware_spec.rb +88 -0
- data/spec/s3/query_string_signer_spec.rb +13 -57
- data/spec/s3/signature_spec.rb +107 -0
- data/spec/s3/string_to_sign_spec.rb +94 -0
- metadata +59 -56
- checksums.yaml.gz.sig +0 -2
- data.tar.gz.sig +0 -0
- data/.travis.yml +0 -7
- data/lib/awsraw/s3.rb +0 -15
- data/lib/awsraw/s3/configuration.rb +0 -12
- data/lib/awsraw/s3/http_request_builder.rb +0 -51
- data/lib/awsraw/s3/md5_digester.rb +0 -19
- data/lib/awsraw/s3/request.rb +0 -65
- data/lib/awsraw/s3/response.rb +0 -26
- data/lib/awsraw/s3/signer.rb +0 -86
- data/spec/s3/client_spec.rb +0 -27
- data/spec/s3/configuration_spec.rb +0 -23
- data/spec/s3/http_request_builder_spec.rb +0 -45
- data/spec/s3/md5_digester_spec.rb +0 -21
- data/spec/s3/request_spec.rb +0 -23
- data/spec/s3/signer_spec.rb +0 -75
- metadata.gz.sig +0 -1
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'faraday'
|
3
|
+
require 'awsraw/error'
|
4
|
+
require 'awsraw/s3/content_md5_header'
|
5
|
+
require 'awsraw/s3/signature'
|
6
|
+
require 'awsraw/s3/string_to_sign'
|
7
|
+
|
8
|
+
module AWSRaw
|
9
|
+
module S3
|
10
|
+
class FaradayMiddleware < Faraday::Middleware
|
11
|
+
|
12
|
+
def initialize(app, credentials = nil)
|
13
|
+
@app = app
|
14
|
+
@credentials = credentials
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
if env[:body] && env[:request_headers]['Content-Type'].nil?
|
19
|
+
raise AWSRaw::Error, "Can't make a request with a body but no Content-Type header"
|
20
|
+
end
|
21
|
+
|
22
|
+
env[:request_headers]['Date'] ||= Time.now.httpdate
|
23
|
+
env[:request_headers]['Content-MD5'] ||= ContentMD5Header.generate_content_md5(env[:body])
|
24
|
+
|
25
|
+
string_to_sign = StringToSign.string_to_sign(
|
26
|
+
:method => env[:method].to_s.upcase,
|
27
|
+
:uri => env[:url],
|
28
|
+
:content_md5 => env[:request_headers]['Content-MD5'],
|
29
|
+
:content_type => env[:request_headers]['Content-Type'],
|
30
|
+
:date => env[:request_headers]['Date'],
|
31
|
+
:amz_headers => env[:request_headers]
|
32
|
+
)
|
33
|
+
|
34
|
+
env[:request_headers]['Authorization'] = Signature.authorization_header(string_to_sign, @credentials)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,50 +1,39 @@
|
|
1
|
-
require 'awsraw/s3/
|
2
|
-
require '
|
1
|
+
require 'awsraw/s3/string_to_sign'
|
2
|
+
require 'awsraw/s3/signature'
|
3
3
|
|
4
4
|
module AWSRaw
|
5
5
|
module S3
|
6
6
|
|
7
|
-
#
|
7
|
+
# Sign S3 URIs using the query string.
|
8
8
|
#
|
9
|
-
# See http://docs.
|
10
|
-
|
11
|
-
# The Authorization header method is usually preferable, as implemented in
|
12
|
-
# AWSRaw::S3::Signer. However, you may have occasions where you need a
|
13
|
-
# simple "download URL", without having to tell your user-agent (browser,
|
14
|
-
# curl, wget, etc) about all the special AWS headers. The query string
|
15
|
-
# authentication method is useful in those cases.
|
16
|
-
class QueryStringSigner < Signer
|
17
|
-
def sign_with_query_string(url, expires, headers = {})
|
18
|
-
query_string_hash = query_string_hash(url, expires, headers)
|
19
|
-
|
20
|
-
uri = URI.parse(url)
|
21
|
-
uri.query = query_string_hash.map { |k,v| "#{k}=#{v}" }.join("&")
|
22
|
-
uri.to_s
|
23
|
-
end
|
24
|
-
|
25
|
-
def query_string_hash(url, expires, headers = {})
|
26
|
-
string_to_sign = string_to_sign(url, expires, headers)
|
27
|
-
signature = encoded_signature(string_to_sign)
|
9
|
+
# See http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
|
10
|
+
class QueryStringSigner
|
28
11
|
|
29
|
-
|
30
|
-
|
31
|
-
"Expires" => expires.to_s,
|
32
|
-
"Signature" => CGI.escape(signature)
|
33
|
-
}
|
12
|
+
def initialize(credentials)
|
13
|
+
@credentials = credentials
|
34
14
|
end
|
35
15
|
|
36
|
-
def
|
37
|
-
|
38
|
-
"GET",
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
16
|
+
def sign(uri, expires)
|
17
|
+
string_to_sign = StringToSign.string_to_sign(
|
18
|
+
:method => "GET",
|
19
|
+
:uri => uri,
|
20
|
+
:date => expires.to_i
|
21
|
+
)
|
22
|
+
|
23
|
+
signature = Signature.signature(string_to_sign, @credentials)
|
24
|
+
|
25
|
+
URI(uri).tap do |signed_uri|
|
26
|
+
signed_uri.query = URI.encode_www_form(
|
27
|
+
"AWSAccessKeyId" => @credentials.access_key_id,
|
28
|
+
"Signature" => signature,
|
29
|
+
"Expires" => expires.to_i
|
30
|
+
)
|
31
|
+
end
|
45
32
|
end
|
46
33
|
|
47
|
-
|
34
|
+
# For backwards-compatibility with pre-1.0 versions:
|
35
|
+
alias_method :sign_with_query_string, :sign
|
48
36
|
|
37
|
+
end
|
49
38
|
end
|
50
39
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module AWSRaw
|
6
|
+
module S3
|
7
|
+
|
8
|
+
module Signature
|
9
|
+
|
10
|
+
# Given a string to sign and some AWS credentials, generate a signature
|
11
|
+
# for an S3 request.
|
12
|
+
def self.signature(string_to_sign, credentials)
|
13
|
+
base64_encode(hmac_sha1(string_to_sign, credentials))
|
14
|
+
end
|
15
|
+
|
16
|
+
# Given a string to sign and some AWS credentials, generate a value
|
17
|
+
# for the Authorization header of an S3 request.
|
18
|
+
def self.authorization_header(string_to_sign, credentials)
|
19
|
+
"AWS #{credentials.access_key_id}:#{signature(string_to_sign, credentials)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Encode a HTML form upload policy. See:
|
23
|
+
# http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTForms.html
|
24
|
+
#
|
25
|
+
# The policy is expected to be a JSON document.
|
26
|
+
def self.encode_form_policy(policy)
|
27
|
+
base64_encode(policy)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sign a policy document for a HTML form upload.
|
31
|
+
#
|
32
|
+
# The policy document is expected to be base64 encoded JSON.
|
33
|
+
# See the .encode_form_policy method.
|
34
|
+
def self.form_signature(policy_base64, credentials)
|
35
|
+
signature(policy_base64, credentials)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.hmac_sha1(data, credentials)
|
41
|
+
digest = OpenSSL::Digest::Digest.new("sha1")
|
42
|
+
OpenSSL::HMAC.digest(digest, credentials.secret_access_key, data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.base64_encode(data)
|
46
|
+
Base64.encode64(data).tr("\n", "")
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'awsraw/s3/canonicalized_resource'
|
2
|
+
|
3
|
+
module AWSRaw
|
4
|
+
module S3
|
5
|
+
|
6
|
+
module StringToSign
|
7
|
+
|
8
|
+
# Generate the string to sign for authentication headers or query string signing, as per:
|
9
|
+
#
|
10
|
+
# http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#ConstructingTheAuthenticationHeader
|
11
|
+
#
|
12
|
+
# Expects the following parameters:
|
13
|
+
#
|
14
|
+
# :method The HTTP method being used, e.g. GET, PUT, DELETE
|
15
|
+
# :uri The full URI for the request, hostname and everything
|
16
|
+
# :content_md5 The Content-MD5 header; required if there is body content
|
17
|
+
# :content_type The Content-Type header
|
18
|
+
# :date The Date (or X-Amz-Date) header
|
19
|
+
# :amz_headers A hash of all the X-Amz-* headers
|
20
|
+
#
|
21
|
+
# Headers in the :amz_headers hash that don't start with "X-Amz-" will be ignored.
|
22
|
+
#
|
23
|
+
# For query string signing, pass in the "Expires" timestamp in the :date parameter.
|
24
|
+
def self.string_to_sign(request_info = {})
|
25
|
+
[
|
26
|
+
request_info[:method],
|
27
|
+
request_info[:content_md5] || "",
|
28
|
+
request_info[:content_type] || "",
|
29
|
+
request_info[:date],
|
30
|
+
canonicalized_amz_headers(request_info[:amz_headers] || {}),
|
31
|
+
CanonicalizedResource.canonicalized_resource(request_info[:uri])
|
32
|
+
].flatten.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.canonicalized_amz_headers(headers)
|
36
|
+
header_names = headers.keys.
|
37
|
+
select {|name| name =~ /^x-amz-/i }.
|
38
|
+
sort_by {|name| name.downcase }
|
39
|
+
|
40
|
+
header_names.map do |name|
|
41
|
+
"#{name.downcase}:#{headers[name]}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
data/lib/awsraw/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "0.1
|
1
|
+
module AWSRaw
|
2
|
+
VERSION = "1.0.0.alpha.1"
|
3
3
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'awsraw/s3/canonicalized_resource'
|
2
|
+
|
3
|
+
describe AWSRaw::S3::CanonicalizedResource do
|
4
|
+
|
5
|
+
context ".canonicalized_resource" do
|
6
|
+
it "works for a virtual-host-style request" do
|
7
|
+
expect(subject.canonicalized_resource("http://johnsmith.s3.amazonaws.com/puppies.jpg")).to eq("/johnsmith/puppies.jpg")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "works for a path-style request" do
|
11
|
+
expect(subject.canonicalized_resource("http://s3.amazonaws.com/johnsmith/puppies.jpg")).to eq("/johnsmith/puppies.jpg")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context ".bucket_from_hostname" do
|
16
|
+
it "gets the bucket from virtual-host-style requests in the default region" do
|
17
|
+
expect(subject.bucket_from_hostname("johnsmith.net.s3.amazonaws.com")).to eq("johnsmith.net")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "gets the bucket from virtual-host-style requests in other regions" do
|
21
|
+
expect(subject.bucket_from_hostname("johnsmith.net.s3-eu-west-1.amazonaws.com")).to eq("johnsmith.net")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "gets the bucket from cname-style requests" do
|
25
|
+
expect(subject.bucket_from_hostname("johnsmith.net")).to eq("johnsmith.net")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "doesn't get the bucket for path-style requests in the default region" do
|
29
|
+
expect(subject.bucket_from_hostname("s3.amazonaws.com")).to be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "doesn't get the bucket for path-style requests in other regions" do
|
33
|
+
expect(subject.bucket_from_hostname("s3-eu-west-1.amazonaws.com")).to be_nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context ".canonicalized_subresources" do
|
38
|
+
it "includes valid subresources" do
|
39
|
+
expect(subject.canonicalized_subresources("acl")).to eq("?acl")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "excludes invalid subresources" do
|
43
|
+
expect(subject.canonicalized_subresources("rhubarb")).to be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "sorts the subresources" do
|
47
|
+
expect(subject.canonicalized_subresources("website&acl")).to eq("?acl&website")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'awsraw/s3/content_md5_header'
|
3
|
+
|
4
|
+
describe AWSRaw::S3::ContentMD5Header do
|
5
|
+
|
6
|
+
context ".generate_content_md5" do
|
7
|
+
it "returns nil if the body is nil" do
|
8
|
+
expect(subject.generate_content_md5(nil)).to be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it "generates the correct digest for a string" do
|
12
|
+
expect(subject.generate_content_md5("rhubarb")).to eq("lBxzvNO0KqwdCwPVMx2IYQ==")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "generates the correct digest for a file" do
|
16
|
+
file = StringIO.new("rhubarb")
|
17
|
+
expect(subject.generate_content_md5(file)).to eq("lBxzvNO0KqwdCwPVMx2IYQ==")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "rewinds a file after reading it" do
|
21
|
+
file = StringIO.new("rhubarb")
|
22
|
+
subject.generate_content_md5(file)
|
23
|
+
expect(file.pos).to eq(0)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'awsraw/s3/faraday_middleware'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe AWSRaw::S3::FaradayMiddleware do
|
5
|
+
|
6
|
+
let(:credentials) do
|
7
|
+
OpenStruct.new(
|
8
|
+
:access_key_id => "AKIAIOSFODNN7EXAMPLE",
|
9
|
+
:secret_access_key => "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:app) { double("app") }
|
14
|
+
let(:time) { Time.parse("2013-09-19 19:22:13 +1000") }
|
15
|
+
|
16
|
+
subject { described_class.new(app, credentials) }
|
17
|
+
|
18
|
+
it "signs the request" do
|
19
|
+
env = {
|
20
|
+
:method => :put,
|
21
|
+
:url => "http://static.johnsmith.net:8080/db-backup.dat.gz",
|
22
|
+
:request_headers => {
|
23
|
+
"Date" => "Tue, 27 Mar 2007 21:06:08 +0000",
|
24
|
+
"Content-Type" => "application/x-download",
|
25
|
+
"Content-MD5" => "4gJE4saaMU4BqNR0kLY+lw==",
|
26
|
+
"x-amz-acl" => "public-read",
|
27
|
+
"X-Amz-Meta-ReviewedBy" => "joe@johnsmith.net,jane@johnsmith.net",
|
28
|
+
"X-Amz-Meta-FileChecksum" => "0x02661779",
|
29
|
+
"X-Amz-Meta-ChecksumAlgorithm" => "crc32"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
subject.call(env)
|
33
|
+
expect(env[:request_headers]["Authorization"]).to eq(
|
34
|
+
"AWS AKIAIOSFODNN7EXAMPLE:ilyl83RwaSoYIEdixDQcA4OnAnc="
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "sets the Date header if there isn't one" do
|
39
|
+
Time.stub(:now => time) # Freeze time for the duration of the test.
|
40
|
+
|
41
|
+
env = {
|
42
|
+
:method => :get,
|
43
|
+
:url => "http://s3.amazonaws.com/",
|
44
|
+
:request_headers => {}
|
45
|
+
}
|
46
|
+
subject.call(env)
|
47
|
+
expect(env[:request_headers]["Date"]).to eq(time.httpdate)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "calculates the Content-MD5 header from the body if there isn't one" do
|
51
|
+
env = {
|
52
|
+
:method => :put,
|
53
|
+
:url => "http://s3.amazonaws.com/johnsmith/my-file.txt",
|
54
|
+
:body => "rhubarb",
|
55
|
+
:request_headers => {
|
56
|
+
"Content-Type" => "text/plain"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
subject.call(env)
|
60
|
+
expect(env[:request_headers]["Content-MD5"]).to eq("lBxzvNO0KqwdCwPVMx2IYQ==")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "lets you manually set the Content-MD5 header" do
|
64
|
+
env = {
|
65
|
+
:method => :put,
|
66
|
+
:url => "http://s3.amazonaws.com/johnsmith/my-file.txt",
|
67
|
+
:body => "rhubarb",
|
68
|
+
:request_headers => {
|
69
|
+
"Content-Type" => "text/plain",
|
70
|
+
"Content-MD5" => "test-content-md5"
|
71
|
+
}
|
72
|
+
}
|
73
|
+
subject.call(env)
|
74
|
+
expect(env[:request_headers]["Content-MD5"]).to eq("test-content-md5")
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
it "blows up if you have a request body, but no content type" do
|
79
|
+
env = {
|
80
|
+
:method => :put,
|
81
|
+
:url => "http://s3.amazonaws.com/johnsmith/my-file.txt",
|
82
|
+
:body => "rhubarb",
|
83
|
+
:request_headers => { }
|
84
|
+
}
|
85
|
+
expect { subject.call(env) }.to raise_error(AWSRaw::Error, "Can't make a request with a body but no Content-Type header")
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -1,67 +1,23 @@
|
|
1
1
|
require 'awsraw/s3/query_string_signer'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
describe AWSRaw::S3::QueryStringSigner do
|
4
|
-
let(:access_key_id) { "AKIAIOSFODNN7EXAMPLE" }
|
5
|
-
let(:secret_access_key) { "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
expiry = 1175139620
|
13
|
-
headers = {}
|
14
|
-
|
15
|
-
subject.string_to_sign(url, expiry, {}).should ==
|
16
|
-
"GET\n\n\n#{expiry}\n/johnsmith/photos/puppy.jpg"
|
17
|
-
|
18
|
-
subject.query_string_hash(url, expiry).should == {
|
19
|
-
"AWSAccessKeyId" => access_key_id,
|
20
|
-
"Expires" => expiry.to_s,
|
21
|
-
"Signature" => "NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D"
|
22
|
-
}
|
23
|
-
|
24
|
-
subject.sign_with_query_string(url, expiry).to_s.should ==
|
25
|
-
"http://s3.amazonaws.com/johnsmith/photos/puppy.jpg?AWSAccessKeyId=#{access_key_id}&Expires=#{expiry}&Signature=NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D"
|
26
|
-
end
|
27
|
-
|
28
|
-
it "signs a get request to a non-us-east bucket" do
|
29
|
-
url = "http://johnsmith.s3.amazonaws.com/photos/puppy.jpg"
|
30
|
-
expiry = 1175139620
|
31
|
-
headers = {}
|
32
|
-
|
33
|
-
subject.string_to_sign(url, expiry, headers).should ==
|
34
|
-
"GET\n\n\n#{expiry}\n/johnsmith/photos/puppy.jpg"
|
35
|
-
|
36
|
-
subject.query_string_hash(url, expiry).should == {
|
37
|
-
"AWSAccessKeyId" => access_key_id,
|
38
|
-
"Expires" => expiry.to_s,
|
39
|
-
"Signature" => "NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D"
|
40
|
-
}
|
41
|
-
|
42
|
-
subject.sign_with_query_string(url, expiry).to_s.should ==
|
43
|
-
"http://johnsmith.s3.amazonaws.com/photos/puppy.jpg?AWSAccessKeyId=#{access_key_id}&Expires=#{expiry}&Signature=NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D"
|
44
|
-
end
|
6
|
+
let(:credentials) do
|
7
|
+
OpenStruct.new(
|
8
|
+
:access_key_id => "AKIAIOSFODNN7EXAMPLE",
|
9
|
+
:secret_access_key => "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
10
|
+
)
|
45
11
|
end
|
46
12
|
|
47
|
-
|
48
|
-
let(:url) { "http://s3.amazonaws.com/johnsmith/" }
|
49
|
-
let(:expiry) { 1175139620 }
|
50
|
-
|
51
|
-
it "changes the signature based on the Content-MD5 header" do
|
52
|
-
subject.string_to_sign(url, expiry, "Content-MD5" => "deadbeef").should ==
|
53
|
-
"GET\ndeadbeef\n\n#{expiry}\n/johnsmith/"
|
54
|
-
end
|
13
|
+
subject { described_class.new(credentials) }
|
55
14
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
15
|
+
# See the example in the AWS docs:
|
16
|
+
# http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
|
17
|
+
it "signs Amazon's example URI correctly" do
|
18
|
+
uri = subject.sign("http://johnsmith.s3.amazonaws.com/photos/puppy.jpg", Time.at(1175139620))
|
60
19
|
|
61
|
-
|
62
|
-
subject.string_to_sign(url, expiry, "x-amz-acl" => "public-read").should ==
|
63
|
-
"GET\n\n\n#{expiry}\nx-amz-acl:public-read\n/johnsmith/"
|
64
|
-
end
|
20
|
+
expect(uri.to_s).to eq("http://johnsmith.s3.amazonaws.com/photos/puppy.jpg?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Signature=NpgCjnDzrM%2BWFzoENXmpNDUsSn8%3D&Expires=1175139620")
|
65
21
|
end
|
66
|
-
end
|
67
22
|
|
23
|
+
end
|