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
@@ -1,19 +0,0 @@
|
|
1
|
-
module AWSRaw
|
2
|
-
module S3
|
3
|
-
class MD5Digester
|
4
|
-
def initialize(string_or_file)
|
5
|
-
@string_or_file = string_or_file
|
6
|
-
end
|
7
|
-
|
8
|
-
def digest
|
9
|
-
if @string_or_file.is_a?(File)
|
10
|
-
Digest::MD5.file(@string_or_file.path).digest
|
11
|
-
elsif @string_or_file.is_a?(String)
|
12
|
-
Digest::MD5.digest(@string_or_file)
|
13
|
-
else
|
14
|
-
raise "Unable to digest #{@string_or_file.class}"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/awsraw/s3/request.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require 'digest/md5'
|
2
|
-
require 'time'
|
3
|
-
require 'uri'
|
4
|
-
|
5
|
-
module AWSRaw
|
6
|
-
module S3
|
7
|
-
|
8
|
-
US_STANDARD = "us-east-1"
|
9
|
-
|
10
|
-
# Note that we use path style (rather than virtual hosted style) requests.
|
11
|
-
# This is because virtual hosted requests only support lower case bucket
|
12
|
-
# names.
|
13
|
-
#
|
14
|
-
# See http://docs.amazonwebservices.com/AmazonS3/latest/dev/VirtualHosting.html
|
15
|
-
class Request
|
16
|
-
def initialize(params, signer)
|
17
|
-
@method = params[:method]
|
18
|
-
@bucket = params[:bucket]
|
19
|
-
@region = params[:region]
|
20
|
-
@key = params[:key]
|
21
|
-
@query = params[:query]
|
22
|
-
@headers = params[:headers] || {}
|
23
|
-
@content = params[:content]
|
24
|
-
|
25
|
-
raise "Content without Content-Type" if !@content.nil? && @headers["Content-Type"].nil?
|
26
|
-
|
27
|
-
headers["Content-MD5"] = content_md5 unless content.nil?
|
28
|
-
headers["Date"] = Time.now.rfc2822
|
29
|
-
headers["Authorization"] = signer.signature(self)
|
30
|
-
end
|
31
|
-
|
32
|
-
attr_reader :method
|
33
|
-
attr_reader :bucket
|
34
|
-
attr_reader :key
|
35
|
-
attr_reader :query
|
36
|
-
attr_reader :headers
|
37
|
-
attr_reader :content
|
38
|
-
|
39
|
-
def host
|
40
|
-
if @region && @region != US_STANDARD
|
41
|
-
S3.configuration.regional_hosts[@region]
|
42
|
-
else
|
43
|
-
S3.configuration.host
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def path
|
48
|
-
@path ||= URI.escape("/#{bucket}#{key}")
|
49
|
-
end
|
50
|
-
|
51
|
-
def uri
|
52
|
-
@uri ||= URI::HTTP.build(
|
53
|
-
:host => host,
|
54
|
-
:path => path,
|
55
|
-
:query => query
|
56
|
-
)
|
57
|
-
end
|
58
|
-
|
59
|
-
def content_md5
|
60
|
-
@content_md5 ||= Base64.encode64(MD5Digester.new(content).digest).strip
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|
65
|
-
end
|
data/lib/awsraw/s3/response.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module AWSRaw
|
2
|
-
module S3
|
3
|
-
|
4
|
-
class Response
|
5
|
-
def initialize(params = {})
|
6
|
-
@code = params[:code]
|
7
|
-
@headers = params[:headers]
|
8
|
-
@content = params[:content]
|
9
|
-
end
|
10
|
-
|
11
|
-
attr_accessor :code
|
12
|
-
attr_accessor :headers
|
13
|
-
attr_accessor :content
|
14
|
-
|
15
|
-
def success?
|
16
|
-
code =~ /^2\d\d$/
|
17
|
-
end
|
18
|
-
|
19
|
-
def failure?
|
20
|
-
!success?
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
data/lib/awsraw/s3/signer.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
require 'digest/sha1'
|
2
|
-
require 'openssl'
|
3
|
-
require 'uri'
|
4
|
-
require 'base64'
|
5
|
-
|
6
|
-
module AWSRaw
|
7
|
-
module S3
|
8
|
-
|
9
|
-
# Generates the Authorization header for a REST request to S3.
|
10
|
-
#
|
11
|
-
# See http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
|
12
|
-
class Signer
|
13
|
-
SUBRESOURCES = %w(acl lifecycle location logging notification partNumber policy requestPayment torrent uploadId uploads versionId versioning versions website)
|
14
|
-
|
15
|
-
def initialize(access_key_id, secret_access_key)
|
16
|
-
@access_key_id = access_key_id
|
17
|
-
@secret_access_key = secret_access_key
|
18
|
-
end
|
19
|
-
|
20
|
-
def authorization_header_value(request)
|
21
|
-
string_to_sign = string_to_sign(request)
|
22
|
-
signature = encoded_signature(string_to_sign)
|
23
|
-
|
24
|
-
"AWS #{@access_key_id}:#{signature}"
|
25
|
-
end
|
26
|
-
|
27
|
-
# Backwards compatibility
|
28
|
-
alias_method :signature, :authorization_header_value
|
29
|
-
|
30
|
-
def encoded_signature(string_to_sign)
|
31
|
-
digest = OpenSSL::Digest.new("sha1")
|
32
|
-
sha = OpenSSL::HMAC.digest(digest, @secret_access_key, string_to_sign)
|
33
|
-
signature = Base64.encode64(sha).strip
|
34
|
-
end
|
35
|
-
|
36
|
-
def string_to_sign(request)
|
37
|
-
[
|
38
|
-
request.method,
|
39
|
-
request.headers["Content-MD5"] || "",
|
40
|
-
request.headers["Content-Type"] || "",
|
41
|
-
request.headers["Date"],
|
42
|
-
canonicalized_amz_headers(request.headers),
|
43
|
-
canonicalized_resource(request)
|
44
|
-
].flatten.join("\n")
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def canonicalized_amz_headers(headers)
|
50
|
-
header_names = headers.keys.
|
51
|
-
select {|name| name =~ /^x-amz-/i }.
|
52
|
-
sort_by {|name| name.downcase }
|
53
|
-
|
54
|
-
header_names.map do |name|
|
55
|
-
"#{name.downcase}:#{headers[name]}"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def canonicalized_resource(request)
|
60
|
-
if request.host =~ /^(.+)\.s3\.amazonaws\.com/
|
61
|
-
bucket = request.host.split(/\./).first
|
62
|
-
resource = '/' + bucket + request.path
|
63
|
-
else
|
64
|
-
resource = request.path
|
65
|
-
end
|
66
|
-
resource + canonicalized_subresource(request)
|
67
|
-
end
|
68
|
-
|
69
|
-
def canonicalized_subresource(request)
|
70
|
-
return "" unless request.query
|
71
|
-
subresources =
|
72
|
-
request.query.split('&')
|
73
|
-
.map { |s| s.split('=') }
|
74
|
-
.select { |k,v| SUBRESOURCES.include? k }
|
75
|
-
.map { |k,v| k + (v ? "=#{v}" : "") }
|
76
|
-
if subresources.any?
|
77
|
-
"?" + subresources.join("&")
|
78
|
-
else
|
79
|
-
""
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
end
|
data/spec/s3/client_spec.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'awsraw/s3/client'
|
2
|
-
|
3
|
-
describe AWSRaw::S3::Client do
|
4
|
-
|
5
|
-
subject { AWSRaw::S3::Client.new("dummy_access_key_id", "dummy_secret_access_key") }
|
6
|
-
|
7
|
-
describe "#request!" do
|
8
|
-
it "returns if the response indicates success" do
|
9
|
-
response = double(:failure? => false)
|
10
|
-
subject.stub(:request => response)
|
11
|
-
|
12
|
-
expect {
|
13
|
-
subject.request!(:method => "PUT")
|
14
|
-
}.to_not raise_error
|
15
|
-
end
|
16
|
-
|
17
|
-
it "raises an error if the response indicates failure" do
|
18
|
-
response = double(:failure? => true)
|
19
|
-
subject.stub(:request => response)
|
20
|
-
|
21
|
-
expect {
|
22
|
-
subject.request!(:method => "PUT")
|
23
|
-
}.to raise_error(::AWSRaw::S3::ConnectionError)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'awsraw/s3/configuration'
|
2
|
-
|
3
|
-
describe AWSRaw::S3::Configuration do
|
4
|
-
subject(:configuration) { AWSRaw::S3::Configuration.new }
|
5
|
-
|
6
|
-
it 'has the default S3 host' do
|
7
|
-
expect(configuration.host).to eq 's3.amazonaws.com'
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'has the default S3 regional host' do
|
11
|
-
expect(configuration.regional_hosts['ap-southeast-2']).to eq 's3-ap-southeast-2.amazonaws.com'
|
12
|
-
end
|
13
|
-
|
14
|
-
describe 'with a custom regional hosts hash' do
|
15
|
-
before do
|
16
|
-
configuration.regional_hosts = { 'ap-southeast-2' => 's3.envato.dev' }
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'returns the custom regional host' do
|
20
|
-
expect(configuration.regional_hosts['ap-southeast-2']).to eq 's3.envato.dev'
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
require 'awsraw/s3/http_request_builder'
|
2
|
-
|
3
|
-
describe AWSRaw::S3::HTTPRequestBuilder do
|
4
|
-
subject do
|
5
|
-
AWSRaw::S3::HTTPRequestBuilder.new(s3_request)
|
6
|
-
end
|
7
|
-
|
8
|
-
let(:content) { "hello world" }
|
9
|
-
let(:s3_request) do
|
10
|
-
double(
|
11
|
-
:uri => URI.parse("http://foo.com/foo"),
|
12
|
-
:headers => {"Content-Type" => "application/not-real"},
|
13
|
-
:content => content,
|
14
|
-
:method => "PUT"
|
15
|
-
)
|
16
|
-
end
|
17
|
-
|
18
|
-
context "content is a string" do
|
19
|
-
let(:content) { "hello world" }
|
20
|
-
it "sets the body on the http request to content" do
|
21
|
-
subject.build.body.should == content
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context "content is a file object" do
|
26
|
-
let(:content) { File.open(__FILE__, "rb") }
|
27
|
-
it "sets the body on the http request to content" do
|
28
|
-
http_request = subject.build
|
29
|
-
http_request.body_stream.should == content
|
30
|
-
http_request['Content-Length'].should == File.size(__FILE__).to_s
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
it "sets the path on the request" do
|
35
|
-
subject.build.path.should == "/foo"
|
36
|
-
end
|
37
|
-
|
38
|
-
it "sets the headers" do
|
39
|
-
subject.build["Content-Type"].should == "application/not-real"
|
40
|
-
end
|
41
|
-
|
42
|
-
it "returns the appropriate request class" do
|
43
|
-
subject.build.class.should == Net::HTTP::Put
|
44
|
-
end
|
45
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'awsraw/s3/md5_digester'
|
3
|
-
|
4
|
-
describe AWSRaw::S3::MD5Digester do
|
5
|
-
|
6
|
-
describe "#digest" do
|
7
|
-
it "returns the md5 digest of the string" do
|
8
|
-
AWSRaw::S3::MD5Digester.new("hello").digest.should == Digest::MD5.digest("hello")
|
9
|
-
end
|
10
|
-
|
11
|
-
it "returns the md5 digest of a file" do
|
12
|
-
file = File.open(__FILE__, "rb")
|
13
|
-
AWSRaw::S3::MD5Digester.new(file).digest.should == Digest::MD5.file(__FILE__).digest
|
14
|
-
end
|
15
|
-
|
16
|
-
it "raises an error on unknown input" do
|
17
|
-
expect { AWSRaw::S3::MD5Digester.new(3).digest }.to raise_error("Unable to digest Fixnum")
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
data/spec/s3/request_spec.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'awsraw/s3/client'
|
2
|
-
|
3
|
-
describe AWSRaw::S3::Request do
|
4
|
-
let(:signer) { double(:signature => "signature") }
|
5
|
-
|
6
|
-
describe "#host" do
|
7
|
-
it "defaults to the standard host" do
|
8
|
-
request = described_class.new({}, signer)
|
9
|
-
request.host.should == "s3.amazonaws.com"
|
10
|
-
end
|
11
|
-
|
12
|
-
it "ignores the region if it is the standard region" do
|
13
|
-
request = described_class.new({ :region => "us-east-1" }, signer)
|
14
|
-
request.host.should == "s3.amazonaws.com"
|
15
|
-
end
|
16
|
-
|
17
|
-
it "uses the region specific host when a non standard region is supplied" do
|
18
|
-
request = described_class.new({ :region => "ap-southeast-2" }, signer)
|
19
|
-
request.host.should == "s3-ap-southeast-2.amazonaws.com"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
data/spec/s3/signer_spec.rb
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
require 'awsraw/s3/signer'
|
2
|
-
|
3
|
-
describe AWSRaw::S3::Signer do
|
4
|
-
let(:access_key_id) { "AKIAIOSFODNN7EXAMPLE" }
|
5
|
-
let(:secret_access_key) { "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }
|
6
|
-
|
7
|
-
subject { AWSRaw::S3::Signer.new(access_key_id, secret_access_key) }
|
8
|
-
|
9
|
-
context "examples from Amazon docs" do
|
10
|
-
context "Example Object GET" do
|
11
|
-
it "signs a get request correctly" do
|
12
|
-
request = double(
|
13
|
-
:method => "GET",
|
14
|
-
:host => "s3.amazonaws.com",
|
15
|
-
:path => "/johnsmith/photos/puppy.jpg",
|
16
|
-
:query => nil,
|
17
|
-
:headers => { "Date" => "Tue, 27 Mar 2007 19:36:42 +0000" }
|
18
|
-
)
|
19
|
-
|
20
|
-
subject.string_to_sign(request).should ==
|
21
|
-
"GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg"
|
22
|
-
|
23
|
-
subject.signature(request).should == "AWS #{access_key_id}:bWq2s1WEIj+Ydj0vQ697zp+IXMU="
|
24
|
-
end
|
25
|
-
|
26
|
-
it "signs an upload correctly" do
|
27
|
-
request = double(
|
28
|
-
:method => "PUT",
|
29
|
-
:host => "s3.amazonaws.com",
|
30
|
-
:path => "/static.johnsmith.net/db-backup.dat.gz",
|
31
|
-
:query => nil,
|
32
|
-
:headers => {
|
33
|
-
"User-Agent" => "curl/7.15.5",
|
34
|
-
"Date" => "Tue, 27 Mar 2007 21:06:08 +0000",
|
35
|
-
"x-amz-acl" => "public-read",
|
36
|
-
"Content-Type" => "application/x-download",
|
37
|
-
"Content-MD5" => "4gJE4saaMU4BqNR0kLY+lw==",
|
38
|
-
"X-Amz-Meta-ReviewedBy" => "joe@johnsmith.net,jane@johnsmith.net",
|
39
|
-
"X-Amz-Meta-FileChecksum" => "0x02661779",
|
40
|
-
"X-Amz-Meta-ChecksumAlgorithm" => "crc32",
|
41
|
-
"Content-Disposition" => "attachment; filename=database.dat",
|
42
|
-
"Content-Encoding" => "gzip",
|
43
|
-
"Content-Length" => "5913339"
|
44
|
-
}
|
45
|
-
)
|
46
|
-
|
47
|
-
subject.string_to_sign(request).should ==
|
48
|
-
"PUT\n4gJE4saaMU4BqNR0kLY+lw==\napplication/x-download\nTue, 27 Mar 2007 21:06:08 +0000\nx-amz-acl:public-read\nx-amz-meta-checksumalgorithm:crc32\nx-amz-meta-filechecksum:0x02661779\nx-amz-meta-reviewedby:joe@johnsmith.net,jane@johnsmith.net\n/static.johnsmith.net/db-backup.dat.gz"
|
49
|
-
|
50
|
-
subject.signature(request).should == "AWS #{access_key_id}:ilyl83RwaSoYIEdixDQcA4OnAnc="
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
context "Example Fetch" do
|
55
|
-
let(:request) do
|
56
|
-
double(
|
57
|
-
:method => "GET",
|
58
|
-
:host => "johnsmith.s3.amazonaws.com",
|
59
|
-
:path => "/",
|
60
|
-
:query => "acl",
|
61
|
-
:headers => { "Date" => "Tue, 27 Mar 2007 19:44:46 +0000" }
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
it "generates the correct string to sign" do
|
66
|
-
subject.string_to_sign(request).should ==
|
67
|
-
"GET\n\n\nTue, 27 Mar 2007 19:44:46 +0000\n/johnsmith/?acl"
|
68
|
-
end
|
69
|
-
|
70
|
-
it "signs the request correctly" do
|
71
|
-
subject.signature(request).should == "AWS #{access_key_id}:c2WLPFtWHVgbEmeEG93a4cG37dM="
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
metadata.gz.sig
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
����R�眝RZ��m�E"������x����N�Z���O�S��R���J;/H��_�%"4P�z����@�b嗺N�?,&�H)R��#Y/M[o��S[�="�Ms���9�\0*�H�ɂ2͈��~5v�)7]�
|