dynamodb 1.1.1 → 1.2.0

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.
data/Gemfile CHANGED
@@ -1,3 +1,2 @@
1
1
  source "https://rubygems.org"
2
- gem "rake"
3
2
  gemspec
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require "rspec/core/rake_task"
3
3
  RSpec::Core::RakeTask.new(:spec)
4
- task :default => :spec
4
+ task :default => :spec
@@ -13,8 +13,10 @@ Gem::Specification.new do |s|
13
13
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
14
  s.homepage = 'http://github.com/groupme/dynamodb'
15
15
 
16
+ s.add_runtime_dependency 'aws4', '0.0.1'
16
17
  s.add_runtime_dependency 'multi_json', '1.3.7'
17
18
 
19
+ s.add_development_dependency 'rake'
18
20
  s.add_development_dependency 'rspec', '2.8.0'
19
21
  s.add_development_dependency 'webmock', '1.8.11'
20
22
  end
@@ -9,8 +9,6 @@ module DynamoDB
9
9
  class ServerError < BaseError; end
10
10
  class AuthenticationError < BaseError; end
11
11
 
12
- Credentials = Struct.new(:access_key_id, :secret_access_key)
13
-
14
12
  require 'dynamodb/version'
15
13
  require 'dynamodb/connection'
16
14
  require 'dynamodb/http_handler'
@@ -1,3 +1,5 @@
1
+ require "aws4/signer"
2
+
1
3
  module DynamoDB
2
4
  # Establishes a connection to Amazon DynamoDB using credentials.
3
5
  class Connection
@@ -14,42 +16,48 @@ module DynamoDB
14
16
  # Create a connection
15
17
  # uri: # default 'https://dynamodb.us-east-1.amazonaws.com/'
16
18
  # timeout: # default 5 seconds
17
- # api_version: # default
19
+ # api_version: # default
18
20
  #
19
21
  def initialize(opts = {})
20
- if opts[:access_key_id] && opts[:secret_access_key]
21
- @credentials = DynamoDB::Credentials.new(opts[:access_key_id], opts[:secret_access_key])
22
- else
22
+ if !(opts[:access_key_id] && opts[:secret_access_key])
23
23
  raise ArgumentError.new("access_key_id and secret_access_key are required")
24
24
  end
25
25
 
26
- @uri = URI(opts[:uri] || "https://dynamodb.us-east-1.amazonaws.com/")
27
26
  set_timeout(opts[:timeout]) if opts[:timeout]
28
-
27
+ @uri = URI(opts[:uri] || "https://dynamodb.us-east-1.amazonaws.com/")
28
+ region = @uri.host.split(".", 4)[1] || "us-east-1"
29
29
  @api_version = opts[:api_version] || "DynamoDB_20111205"
30
+ @signer = AWS4::Signer.new(
31
+ access_key: opts[:access_key_id],
32
+ secret_key: opts[:secret_access_key],
33
+ region: region
34
+ )
30
35
  end
31
36
 
32
37
  # Create and send a request to DynamoDB
33
38
  #
34
39
  # This returns either a SuccessResponse or a FailureResponse.
35
40
  #
36
- # `operation` can be any DynamoDB operation. `data` is a hash that will be
41
+ # `operation` can be any DynamoDB operation. `body` is a hash that will be
37
42
  # used as the request body (in JSON format). More info available at:
38
43
  # http://docs.amazonwebservices.com/amazondynamodb/latest/developerguide
39
44
  #
40
- def post(operation, data={})
45
+ def post(operation, body={})
41
46
  request = DynamoDB::Request.new(
42
- uri: @uri,
43
- credentials: @credentials,
44
- api_version: @api_version,
45
- operation: operation,
46
- data: data
47
+ signer: @signer,
48
+ uri: @uri,
49
+ operation: version(operation),
50
+ body: body
47
51
  )
48
52
  http_handler.handle(request)
49
53
  end
50
54
 
51
55
  private
52
56
 
57
+ def version(op)
58
+ "#{@api_version}.#{op}"
59
+ end
60
+
53
61
  def http_handler
54
62
  self.class.http_handler
55
63
  end
@@ -1,107 +1,42 @@
1
- require "base64"
2
- require "openssl"
3
-
4
1
  module DynamoDB
5
2
  class Request
6
- class << self
7
- def digest(signing_string, key)
8
- Base64.encode64(
9
- OpenSSL::HMAC.digest('sha256', key, Digest::SHA256.digest(signing_string))
10
- ).strip
11
- end
12
- end
3
+ RFC1123 = "%a, %d %b %Y %H:%M:%S GMT"
4
+ ISO8601 = "%Y%m%dT%H%M%SZ"
13
5
 
14
- attr_reader :uri, :datetime, :credentials, :region, :data, :service, :operation, :api_version
6
+ attr_reader :uri, :operation, :body, :signer
15
7
 
16
8
  def initialize(args = {})
17
- @uri = args[:uri]
18
- @credentials = args[:credentials]
19
- @operation = args[:operation]
20
- @data = args[:data]
21
- @api_version = args[:api_version]
22
- @region = args[:region] || "us-east-1"
23
- @datetime = Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
24
- @service = "dynamodb"
25
- end
26
-
27
- def our_headers
28
- {
29
- "user-agent" => "groupme/dynamodb",
30
- "host" => uri.host,
31
- "content-type" => "application/x-amz-json-1.0",
32
- "content-length" => body.size,
33
- "x-amz-date" => datetime,
34
- "x-amz-target" => "#{api_version}.#{operation}",
35
- "x-amz-content-sha256" => hexdigest(body || '')
36
- }
9
+ @uri = args[:uri]
10
+ @operation = args[:operation]
11
+ @body = MultiJson.dump(args[:body])
12
+ @signer = args[:signer]
37
13
  end
38
14
 
39
15
  def headers
40
- @headers ||= our_headers.merge("authorization" => authorization)
41
- end
42
-
43
- def body
44
- @body ||= MultiJson.dump(data)
45
- end
46
-
47
- def authorization
48
- parts = []
49
- parts << "AWS4-HMAC-SHA256 Credential=#{credentials.access_key_id}/#{credential_string}"
50
- parts << "SignedHeaders=#{our_headers.keys.sort.join(";")}"
51
- parts << "Signature=#{signature}"
52
- parts.join(', ')
53
- end
54
-
55
- def signature
56
- k_secret = credentials.secret_access_key
57
- k_date = hmac("AWS4" + k_secret, datetime[0,8])
58
- k_region = hmac(k_date, region)
59
- k_service = hmac(k_region, service)
60
- k_credentials = hmac(k_service, 'aws4_request')
61
- hexhmac(k_credentials, string_to_sign)
62
- end
63
-
64
- def string_to_sign
65
- parts = []
66
- parts << 'AWS4-HMAC-SHA256'
67
- parts << datetime
68
- parts << credential_string
69
- parts << hexdigest(canonical_request)
70
- parts.join("\n")
71
- end
72
-
73
- def credential_string
74
- parts = []
75
- parts << datetime[0,8]
76
- parts << region
77
- parts << service
78
- parts << 'aws4_request'
79
- parts.join("/")
80
- end
81
-
82
- def canonical_request
83
- parts = []
84
- parts << "POST"
85
- parts << uri.path
86
- parts << uri.query
87
- parts << our_headers.sort.map {|k, v| [k,v].join(':')}.join("\n") + "\n"
88
- parts << "content-length;content-type;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target"
89
- parts << our_headers['x-amz-content-sha256']
90
- parts.join("\n")
16
+ @headers ||= signed_headers
17
+ end
18
+
19
+ private
20
+
21
+ def signed_headers
22
+ date = Time.now.utc
23
+ h = {
24
+ "Date" => date.strftime(RFC1123),
25
+ "User-Agent" => "groupme/dynamodb",
26
+ "Host" => uri.host,
27
+ "Content-Type" => "application/x-amz-json-1.0",
28
+ "Content-Length" => body.size.to_s,
29
+ "X-AMZ-Date" => date.strftime(ISO8601),
30
+ "X-AMZ-Target" => operation,
31
+ "X-AMZ-Content-SHA256" => hexdigest(body || '')
32
+ }
33
+ signer.sign("POST", uri, h, body)
91
34
  end
92
35
 
93
- def hexdigest value
36
+ def hexdigest(value)
94
37
  digest = Digest::SHA256.new
95
38
  digest.update(value)
96
39
  digest.hexdigest
97
40
  end
98
-
99
- def hmac key, value
100
- OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, value)
101
- end
102
-
103
- def hexhmac key, value
104
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha256'), key, value)
105
- end
106
41
  end
107
42
  end
@@ -1,3 +1,3 @@
1
1
  module DynamoDB
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -1,31 +1,32 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe DynamoDB::Request do
4
- let(:uri) { URI("https://dynamodb.us-east-1.amazonaws.com/") }
5
- let(:credentials) { DynamoDB::Credentials.new("access_key_id", "secret_access_key") }
6
- let(:data) { {} }
7
- let(:request) {
8
- DynamoDB::Request.new(
9
- uri: uri,
10
- api_version: "DynamoDB_20111205",
11
- credentials: credentials,
12
- data: data,
13
- operation: "ListTables",
14
- )
15
- }
16
-
17
4
  it "signs the request" do
18
5
  Time.stub(now: Time.parse("20130508T201304Z"))
19
6
 
7
+ uri = URI("https://dynamodb.us-east-1.amazonaws.com/")
8
+ signer = AWS4::Signer.new(
9
+ access_key: "access_key_id",
10
+ secret_key: "secret_access_key",
11
+ region: "us-east-1"
12
+ )
13
+ request = DynamoDB::Request.new(
14
+ uri: uri,
15
+ signer: signer,
16
+ body: {},
17
+ operation: "DynamoDB_20111205.ListTables",
18
+ )
19
+
20
20
  request.headers.should == {
21
- "content-type"=>"application/x-amz-json-1.0",
22
- "x-amz-target"=>"DynamoDB_20111205.ListTables",
23
- "content-length"=>2,
24
- "user-agent"=>"groupme/dynamodb",
25
- "host"=>"dynamodb.us-east-1.amazonaws.com",
26
- "x-amz-date"=>"20130508T201304Z",
27
- "x-amz-content-sha256"=>"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
28
- "authorization"=>"AWS4-HMAC-SHA256 Credential=access_key_id/20130508/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=52fab5c720b35ce247f8388c5c8f66b300074ae88b666985ee26ca70d923be1d"
21
+ "Content-Type"=>"application/x-amz-json-1.0",
22
+ "Content-Length"=>"2",
23
+ "Date"=>"Wed, 08 May 2013 20:13:04 GMT",
24
+ "User-Agent"=>"groupme/dynamodb",
25
+ "Host"=>"dynamodb.us-east-1.amazonaws.com",
26
+ "X-AMZ-Target"=>"DynamoDB_20111205.ListTables",
27
+ "X-AMZ-Date"=>"20130508T201304Z",
28
+ "X-AMZ-Content-SHA256"=>"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
29
+ "Authorization"=>"AWS4-HMAC-SHA256 Credential=access_key_id/20130508/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;date;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=9e551627237673b4deaa2c22fd3b3777d6d0705facd35b56b289e357c4995c46"
29
30
  }
30
31
  end
31
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamodb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,8 +10,24 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-05-08 00:00:00.000000000 Z
13
+ date: 2013-05-17 00:00:00.000000000 Z
14
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws4
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - '='
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - '='
29
+ - !ruby/object:Gem::Version
30
+ version: 0.0.1
15
31
  - !ruby/object:Gem::Dependency
16
32
  name: multi_json
17
33
  requirement: !ruby/object:Gem::Requirement
@@ -28,6 +44,22 @@ dependencies:
28
44
  - - '='
29
45
  - !ruby/object:Gem::Version
30
46
  version: 1.3.7
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
31
63
  - !ruby/object:Gem::Dependency
32
64
  name: rspec
33
65
  requirement: !ruby/object:Gem::Requirement