awsraw 0.1.9 → 1.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|