rubizon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ gem "ruby-hmac", "~> 0.4.0", :require => "ruby_hmac"
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development do
8
+ gem "shoulda", ">= 0"
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.1"
11
+ gem "rcov", ">= 0"
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.5.1)
6
+ bundler (~> 1.0.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.8.7)
10
+ rcov (0.9.9)
11
+ ruby-hmac (0.4.0)
12
+ shoulda (2.11.3)
13
+
14
+ PLATFORMS
15
+ x86-mswin32
16
+
17
+ DEPENDENCIES
18
+ bundler (~> 1.0.0)
19
+ jeweler (~> 1.5.1)
20
+ rcov
21
+ ruby-hmac (~> 0.4.0)
22
+ shoulda
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Randy McLaughlin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,119 @@
1
+ = rubizon
2
+
3
+ A Ruby interface to Amazon Web Services. Rubizon separates creating a
4
+ properly-formed, signed URL for making an AWS request from the transport
5
+ mechanism used. The same logic can thus be used to access AWS using
6
+ Net::HTTP, EventMachine::Protocols::HttpClient or some other transport.
7
+
8
+ In its initial implementation, Rubizon simply builds and signs URLs. Further
9
+ development may include adapters to various transport mechanisms and
10
+ interpretation of results. On the other hand, it may turn out to be best
11
+ kept merely as a URL generator working in concert with other libraries that
12
+ provide transport and result interpretation.
13
+
14
+ ===Class structure
15
+ Rubizon is comprised of a few foundation classes, described below, as well as
16
+ classes for each of the AWS services it supports.
17
+
18
+ *SecurityCredentials encapsulates an AWS Access Key ID and the corresponding
19
+ Secret Access Key. It allows querying the access key and signing an
20
+ arbitrary key, but does not support quering the access key. Only a single
21
+ instance of SecurityCredentials need be created for each key pair.
22
+
23
+ *AbstractSig2Product is intended to provided a foundation for building
24
+ requests to any service that supports signature version 2. The
25
+ SimpleNotificationService (require 'product/sns') class is a concrete
26
+ subclass supporting SNS. Similar classes should be able to similarly
27
+ subclass AbstractSig2Product in order to support other AWS services.
28
+ Only a single instance of any product's class should be required to
29
+ serve any number of requests using the same credentials, host and scheme.
30
+
31
+ *Request encapsulates one request, the code to sign it, formulate a URL and
32
+ to access the URL and its component parts. A product's class will create
33
+ a Request every time a URL is to be generated, provide it with the proper
34
+ values to perform a requested action and then return the request object.
35
+ The URL and its components can then be queried from the request object.
36
+
37
+ Since the ultimate intent of Rubizon is to support requests to many of the AWS
38
+ services, even though a given application may only need access to a small number
39
+ of the supported services, the main rubizon.rb file only requires the core
40
+ code. An interface for each AWS product are maintained in the rubizon/product
41
+ directory and will need to be specifically required into any code needing its
42
+ services.
43
+
44
+ ===An example of publishing a message via SNS:
45
+ <tt>
46
+ require 'rubizon'
47
+ require 'rubizon/product/sns'
48
+ require 'net/http'
49
+ require 'URI'
50
+ credentials= Rubizon::SecurityCredentials.new '00000000000000000000','1234567890'
51
+ sns= Rubizon::SimpleNotificationService.new credentials,'sns.us-east-1.amazonaws.com'
52
+ topic= sns.topic 'arn:aws:sns:us-east-1:123123123123:sample-notifications'
53
+ req=topic.publish 'this is a hello world message','hello world'
54
+ Net::HTTP.get_print URI.parse(req.url)
55
+ </tt>
56
+
57
+ ===Supported AWS services
58
+ The initial implementation also simply scratches the author's itch: the need
59
+ to send a message to SNS. The design should lend itself to a broader
60
+ range of requests and services, but these can be added as needed. The critical
61
+ feature is being able to sign a request and the code to do so should be
62
+ applicable to requests for any service that supports signature version 2 requests,
63
+ including the following services:
64
+ *EC2
65
+ *Elastic MapReduce
66
+ *Auto Scaling
67
+ *SimpleDB
68
+ *RDS
69
+ *Identity and Access Management
70
+ *SQS
71
+ *SNS
72
+ *CloudWatch
73
+ *Virtual Private Cloud
74
+ *Elastic Load Balancing
75
+ *FPS
76
+ *AWS Import/Export
77
+
78
+ There are other AWS services that expect requests to be signed differently.
79
+ I'm not sure if there is a definitive reference to what "signature version 1"
80
+ is but there is definite similarity. I suspect that an AbstractSig1Product
81
+ class could be created using the AbstractSig2Product model that would cover
82
+ 80% of the remaining services. Here are some notes from a brief exploration
83
+ of the API docs:
84
+ *http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/index.html?RESTAuthentication.html
85
+ Cloud Front authorizes only the timestamp, using SHA1 only, and places in an
86
+ "Authorization" header
87
+
88
+ *Route 53 uses X-Amzn-Authorization header, similar to CloudFront's
89
+ Authorization header
90
+
91
+ *S3 also uses an Authorization header
92
+
93
+ *DevPay uses signature version 1
94
+
95
+ *Alexa Web Information Service uses something like signature version 1
96
+ (key id, timestamp and signature)
97
+
98
+ *Mechanical Turk uses something like signature version 1
99
+
100
+ It also appears that the services that support signature version 2 rely almost
101
+ exclusively on HTTP GETs, rather than POSTs, PUTs and DELETEs. Rubizon, as
102
+ currently written, only supports GETs. The Request class could likely be extended
103
+ to support the other REST verbs if there is enough interest and need.
104
+
105
+ == Contributing to rubizon
106
+
107
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
108
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
109
+ * Fork the project
110
+ * Start a feature/bugfix branch
111
+ * Commit and push until you are happy with your contribution
112
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
113
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
114
+
115
+ == Copyright
116
+
117
+ Copyright (c) 2010 Randy McLaughlin. See LICENSE.txt for
118
+ further details.
119
+
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "rubizon"
16
+ gem.homepage = "http://github.com/randymized/rubizon"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{A Ruby interface to Amazon Web Services}
19
+ gem.description = %Q{A Ruby interface to Amazon Web Services. Rubizon separates creating a
20
+ properly-formed, signed URL for making an AWS request from the transport
21
+ mechanism used.
22
+
23
+ In its initial implementation, Rubizon simply builds and signs URLs. Further
24
+ development may include adapters to various transport mechanisms and
25
+ interpretation of results.
26
+ }
27
+ gem.email = "ot40ddj02@sneakemail.com"
28
+ gem.authors = ["Randy McLaughlin"]
29
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
30
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
31
+ gem.add_dependency 'ruby-hmac', '~> 0.4.0'
32
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
33
+ end
34
+ Jeweler::RubygemsDotOrgTasks.new
35
+
36
+ require 'rake/testtask'
37
+ Rake::TestTask.new(:test) do |test|
38
+ test.libs << 'lib' << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ require 'rcov/rcovtask'
44
+ Rcov::RcovTask.new do |test|
45
+ test.libs << 'test'
46
+ test.pattern = 'test/**/test_*.rb'
47
+ test.verbose = true
48
+ end
49
+
50
+ task :default => :test
51
+
52
+ require 'rake/rdoctask'
53
+ Rake::RDocTask.new do |rdoc|
54
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
55
+
56
+ rdoc.rdoc_dir = 'rdoc'
57
+ rdoc.title = "rubizon #{version}"
58
+ rdoc.rdoc_files.include('README*')
59
+ rdoc.rdoc_files.include('lib/**/*.rb')
60
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,126 @@
1
+ require 'cgi'
2
+ require File.dirname(__FILE__) + '/request'
3
+ module Rubizon
4
+ # An abstract representation of an AWS product whose REST API uses
5
+ # signature version 2. This class provides a foundation for
6
+ # classes that represent specific AWS products.
7
+ class AbstractSig2Product
8
+ # Initialization
9
+ #
10
+ # specs - A Hash containing specifications for the product.
11
+ # :scheme - (optional) Default scheme is https.
12
+ # May be set to "http".
13
+ # :host - (conditionally required) If an ARN is not
14
+ # specified or if the host cannot be properly
15
+ # deduced from the ARN, :host must be specified.
16
+ # If specified, this will override any host name
17
+ # that might be deduced from the ARN.
18
+ # :ARN - (optional) An ARN may be specified instead of a
19
+ # host. The host can be deduced from the ARN.
20
+ # :path - (optional) Default path is '/'. May be set to
21
+ # a path that applies to all requests for the
22
+ # product. Additional path elements may be appended
23
+ # if needed by an operation or subject of that
24
+ # operation
25
+ # :URL - (optional) A URL may be specified instead of the
26
+ # individual scheme, host and path elements. If
27
+ # a URL is specified, it will override the individual
28
+ # elements.
29
+ # :_omit - (optional) An array containing a list of elements
30
+ # that are not to be included in the query string.
31
+ # This, for example, can be used in the Product
32
+ # Advertising API to suppress the SignatureMethod
33
+ # and SignatureVersion parameter/value pairs which
34
+ # result from the signing process but are not
35
+ # supported in that API
36
+ # (other) - (optional) Other key/value pairs may be specified.
37
+ # These will be included in any query string
38
+ # generated for this product
39
+ def initialize(specs={})
40
+ @scheme= (specs.delete(:scheme) || specs.delete('scheme') || 'https').to_s
41
+ @arn= specs.delete(:ARN) || specs.delete('ARN') || specs.delete(:arn) || specs.delete('arn')
42
+ @host= specs.delete(:host) || specs.delete('host')
43
+ @url= specs.delete(:URL) || specs.delete('URL') || specs.delete(:url) || specs.delete('url')
44
+ @path= specs.delete(:path) || specs.delete('path') || '/'
45
+ if (@url)
46
+ require 'uri'
47
+ url = URI.parse(@url)
48
+ @scheme= url.scheme
49
+ @host= url.host
50
+ @path= url.path
51
+ end
52
+ if @arn && !@host
53
+ @host= self.class.host_from_ARN(@arn)
54
+ end
55
+ if !@host
56
+ raise InvalidParameterError, 'No host was specified and one could not be deduced from arn specifications'
57
+ end
58
+ @query_elements= specs
59
+ @query_elements['SignatureMethod']= 'HmacSHA256'
60
+ @query_elements['SignatureVersion']= 2
61
+ @query_elements['arn']= @arn if @arn
62
+ end
63
+
64
+ # Default method for calculating the name of the host associated with a given
65
+ # Amazon Resource Name (ARN). This may vary from product to product, so
66
+ # any product with a different mapping from ARN to hostname should override
67
+ # this method.
68
+ #
69
+ # arn - A String containing an Amazon Resource Name (ARN).
70
+ #
71
+ # Returns the name of the host hosting the named resource.
72
+ def self.host_from_ARN(arn)
73
+ elems= arn.split(':',5)
74
+ "#{elems[2]}.#{elems[3]}.amazonaws.com"
75
+ end
76
+
77
+ # Returns the URL scheme, such as http or https
78
+ attr_reader :scheme
79
+
80
+ # Returns the ARN (Amazon Resource Name) served by this object.
81
+ # Returns nil if an ARN is not defined.
82
+ attr_reader :arn
83
+
84
+ # Returns the host (domain and subdomains) of the product served by this
85
+ # object.
86
+ attr_reader :host
87
+
88
+ # Returns the path part of the URL of the product served by this object,
89
+ # typically '/'.
90
+ attr_reader :path
91
+
92
+ # Returns a Hash containing elements to be included in any query string
93
+ # generated for this product. To this will be added elements identifying
94
+ # the specific action, the subject of the action, the access key, and
95
+ # elements related to signing the request.
96
+ attr_reader :query_elements
97
+
98
+ # Returns the product's endpoint. The endpoint is that part of a URL that
99
+ # includes the scheme, host, and path, but not the query string.
100
+ # An action may extend the product's path, but otherwise would retain the
101
+ # rest of the endpoint.
102
+ def endpoint
103
+ "#{@scheme}://#{@host}#{@path}"
104
+ end
105
+
106
+ # Create a Request object that can be used to formulate a single request
107
+ # for this product.
108
+ #
109
+ # Returns an instance of Request
110
+ def create_request(credentials)
111
+ Request.new(credentials,@scheme,@host,@path,@query_elements)
112
+ end
113
+
114
+ protected
115
+ # In most cases, an action method needs only to invoke this method.
116
+ # It will create a Request object, add query elements for the action and
117
+ # its subject and return the request object. The URL, query string,
118
+ # hostname and other information needed to get (or post, put or delete)
119
+ # a resource from AWS can then be obtained via Request methods.
120
+ def basic_action(action_elements,subject_elements=nil)
121
+ req= create_request(@credentials)
122
+ req.add_query_elements(action_elements)
123
+ req.add_query_elements(subject_elements) if subject_elements
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,11 @@
1
+ module Rubizon
2
+ class RubizonError < StandardError; end
3
+ class UnsupportedSignatureMethodError < StandardError; end
4
+ class UnsupportedSignatureVersionError < StandardError; end
5
+ class InvalidParameterError < RubizonError
6
+ attr_reader :message
7
+ def initialize(message='An invalid parameter is in the request')
8
+ @message= message
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ require 'cgi'
2
+ require File.dirname(__FILE__) + "/../abstract_sig2_product"
3
+ module Rubizon
4
+ # Define a class that generates requests for operations on the Product
5
+ # Advertising API
6
+ #
7
+ class ProductAdvertisingProduct < AbstractSig2Product
8
+ # Initialize the product interface.
9
+ #
10
+ # credentials - A SecurityCredentials object that encapsulates the
11
+ # access and secret ids to be used for this product.
12
+ # scheme - (optional - default: http) May set to 'https' if supported.
13
+ def initialize(credentials,scheme='http')
14
+ super(
15
+ :scheme=>scheme,
16
+ :host=>'webservices.amazon.com',
17
+ :path=>'/onca/xml',
18
+ '_omit' => ['SignatureMethod','SignatureVersion'],
19
+ 'Service'=>'AWSECommerceService'
20
+ )
21
+ @credentials= credentials
22
+ end
23
+
24
+ # Create a request for an item lookup. The URL to use may be obtained
25
+ # from the request.
26
+ #
27
+ # params - Parameters for the specific request as key/value pairs.
28
+ def item_lookup_request(subject_elements={})
29
+ basic_action(
30
+ @item_lookup_elements||= {'Operation'=>'ItemLookup'},
31
+ subject_elements
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,71 @@
1
+ require 'cgi'
2
+ require File.dirname(__FILE__) + "/../abstract_sig2_product"
3
+ module Rubizon
4
+ # Define a class that generates requests for operations on a topic of the
5
+ # Simple Notification Service (SNS).
6
+ #
7
+ class SimpleNotificationService < AbstractSig2Product
8
+ # Initialize the SNS interface. Each instance supports requests to one
9
+ # topic.
10
+ #
11
+ # credentials - A SecurityCredentials object that encapsulates the
12
+ # access and secret ids to be used for this product.
13
+ # arn - The topic served by this object
14
+ # scheme - (optional - default: http) May set to 'https' if supported.
15
+ def initialize(credentials,host,scheme='http')
16
+ super(
17
+ :scheme=>scheme,
18
+ :host=>host
19
+ )
20
+ @credentials= credentials
21
+ end
22
+
23
+ # Create a Request object that can be used to formulate a single request
24
+ # for this product.
25
+ #
26
+ # Returns an instance of Request
27
+ def create_request
28
+ super(@credentials)
29
+ end
30
+
31
+ class Topic
32
+ # Specify the topic.
33
+ #
34
+ # sns - An instance of the SimpleNotificationService class
35
+ # arn - Specify the topic's ARN
36
+ def initialize(sns,arn)
37
+ @sns= sns
38
+ @arn= arn
39
+ end
40
+
41
+ # Publish a message to the topic.
42
+ #
43
+ # message - The message you want to send to the topic.
44
+ # subject - Optional parameter to be used as the "Subject" line of when the message is delivered to e-mail endpoints.
45
+ #
46
+ # Returns the Request object. The url, and its elements may be obtained
47
+ # from the returned request object.
48
+ def publish(message,subject=nil)
49
+ request= create_request
50
+ request.add_query_elements('Action'=>'Publish','Message'=>message)
51
+ request.add_query_elements('Subject'=>subject) if subject
52
+ request
53
+ end
54
+
55
+ protected
56
+ def create_request
57
+ @sns.create_request.add_query_elements('TopicArn'=>@arn)
58
+ end
59
+ end
60
+
61
+ # Define a topic.
62
+ #
63
+ # arn - The ARN of the topic
64
+ #
65
+ # Returns a SimpleNotificationService::Topic object, which includes methods
66
+ # for making requests of and for the topic.
67
+ def topic(arn)
68
+ Topic.new(self,arn)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,149 @@
1
+ require 'cgi'
2
+ module Rubizon
3
+ # Represents a request to be made to AWS.
4
+ #
5
+ # Starts with access credentials and specifications for the product,
6
+ # such as its ARN, elements of its URL and query elements.
7
+ # To this is added specifications for the specific action to be performed.
8
+ #
9
+ # Request builds a URL for the action and signs the request. It then
10
+ # makes the entire URL and various components of it available for whatever
11
+ # transport mechanism is to be used.
12
+ #
13
+ # A new instance of Request is to be created for each request sent to AWS
14
+ class Request
15
+ # Initialize the request.
16
+ #
17
+ # credentials - A Rubizon::SecurityCredentials object that
18
+ # encapsulates an AWS key pair.
19
+ # It will be used to sign the request.
20
+ # scheme - The scheme to be used: 'http' or 'https'
21
+ # host - The name of the HTTP host to serve this request
22
+ # path - The URL path, typically '/'.
23
+ # query_elements - A hash of key/value pairs to be included in the
24
+ # query string.
25
+ # _omit - If an element with a key of '_omit' is included,
26
+ # the value must be an array containing names of keys
27
+ # to be omitted from the query_elements array before
28
+ # the request is signed and the query string formed.
29
+ # This allows allowing SignatureMethod or
30
+ # SignatureVersion to be specified, even if they are
31
+ # not to be included in the query string.
32
+ # SignatureVersion - The signature version to be used, such
33
+ # as 1 or 2. This should be a numeric value. If not
34
+ # present, signature version 1 is implied.
35
+ # SignatureMethod - The signature methods defined for version
36
+ # two are HmacSHA256 and HmacSHA1.
37
+ def initialize(credentials,scheme,host,path,query_elements={})
38
+ @credentials= credentials
39
+ @scheme= scheme
40
+ @host= host
41
+ @path= path
42
+ @query_elements= query_elements.dup
43
+ end
44
+
45
+ # Append additional elements to the currently defined path.
46
+ def append_to_path(path)
47
+ @path+= path
48
+ end
49
+
50
+ # Replace the currently defined path with the one given.
51
+ def path=(path)
52
+ @path= path
53
+ end
54
+
55
+ # Add key/value pairs to the query_elements. Typically, these additional
56
+ # elements will be added to specify the action to be taken or subject of
57
+ # that action and parameters of that action or subject.
58
+ #
59
+ # query_elements - A Hash containing key/value pairs to be added to the
60
+ # query string.
61
+ #
62
+ # returns self
63
+ def add_query_elements(query_elements)
64
+ @query_elements.merge! query_elements
65
+ self
66
+ end
67
+
68
+ # Returns the URL scheme, such as http or https
69
+ attr_reader :scheme
70
+
71
+ # Returns the host (domain and subdomains) of the product served by this
72
+ # object.
73
+ attr_reader :host
74
+
75
+ # Returns the path part of the URL of the product served by this object,
76
+ # typically '/'.
77
+ attr_reader :path
78
+
79
+ # Returns the product's endpoint. The endpoint is that part of a URL that
80
+ # includes the scheme, host, and path, but not the query string.
81
+ # An action may extend the product's path, but otherwise would retain the
82
+ # rest of the endpoint.
83
+ def endpoint
84
+ @endpoint||= "#{@scheme}://#{@host}#{@path}"
85
+ end
86
+
87
+ # Create a query string from a hash and sign it.
88
+ # The signature algorithm will be determined from the query elements,
89
+ # such as SignatureVersion
90
+ #
91
+ # The query string, once created, is immutable.
92
+ def query_string
93
+ return @query_string ||=
94
+ if @query_elements['SignatureVersion'].to_i == 2
95
+ query_string_sig2
96
+ else
97
+ raise UnsupportedSignatureVersionError, 'Only signature version 2 requests are supported at this time'
98
+ end
99
+ end
100
+
101
+ # Returns the full URL
102
+ #
103
+ # The query string portion of the URL, once created, is immutable.
104
+ def url
105
+ endpoint+'?'+query_string
106
+ end
107
+
108
+ # An artifact of the signature version 2 signing process:
109
+ # The query string portion of the string to sign.
110
+ # This is of possible debugging value.
111
+ attr_reader :canonical_querystring
112
+
113
+ # An artifact of the signature version 2 signing process:
114
+ # The string that is used to calculate the signature.
115
+ # This is of possible debugging value.
116
+ attr_reader :string_to_sign
117
+
118
+ protected
119
+ # Create a query string and sign it using the signature version 2 algorithm.
120
+ def query_string_sig2
121
+ @query_elements['Timestamp']= Time::at(Time.now).utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless @query_elements['Timestamp']
122
+ @query_elements['AWSAccessKeyId']= @credentials.accessID
123
+ signature_method= @query_elements['SignatureMethod']
124
+ if @query_elements['_omit']
125
+ @query_elements['_omit'].each do |k|
126
+ @query_elements.delete k
127
+ end
128
+ @query_elements.delete '_omit'
129
+ end
130
+ values = @query_elements.keys.sort.collect {|key| [url_encode(key), url_encode(@query_elements[key])].join("=") }
131
+ @canonical_querystring= values.join("&")
132
+ @string_to_sign = <<"____".rstrip
133
+ GET
134
+ #{URI::parse(endpoint).host}
135
+ #{URI::parse(endpoint).path}
136
+ #{@canonical_querystring}
137
+ ____
138
+ signature= @credentials.sign(signature_method,@string_to_sign)
139
+ @query_elements['Signature'] = signature
140
+ @query_elements.collect { |key, value| [url_encode(key), url_encode(value)].join("=") }.join('&') # order doesn't matter for the actual request
141
+ end
142
+ def url_encode(string)
143
+ string = string.to_s
144
+ # It's kind of like CGI.escape, except CGI.escape is encoding a tilde when
145
+ # it ought not to be, so we turn it back. Also space NEEDS to be %20 not +.
146
+ return CGI.escape(string).gsub("%7E", "~").gsub("+", "%20")
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,25 @@
1
+ require 'hmac-sha2'
2
+ require 'base64'
3
+ require File.dirname(__FILE__) + '/exceptions'
4
+ module Rubizon
5
+ class SecurityCredentials
6
+ attr_reader :accessID
7
+ def initialize(accessID, secretID)
8
+ @accessID= accessID
9
+ @secretID= secretID
10
+ end
11
+ def sign256(string_to_sign)
12
+ @hmac256||= HMAC::SHA256.new(@secretID)
13
+ @hmac256.update(string_to_sign)
14
+ Base64.encode64(@hmac256.digest).chomp
15
+ end
16
+ def sign(signature_method,string_to_sign)
17
+ case signature_method
18
+ when 'HmacSHA256'
19
+ sign256(string_to_sign)
20
+ else
21
+ raise UnsupportedSignatureMethodError, "The #{signature_method} signature method is not supported"
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/rubizon.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + "/rubizon/security_credentials"
2
+ require File.dirname(__FILE__) + "/rubizon/request"
data/rubizon.gemspec ADDED
@@ -0,0 +1,91 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rubizon}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Randy McLaughlin"]
12
+ s.date = %q{2011-01-03}
13
+ s.description = %q{A Ruby interface to Amazon Web Services. Rubizon separates creating a
14
+ properly-formed, signed URL for making an AWS request from the transport
15
+ mechanism used.
16
+
17
+ In its initial implementation, Rubizon simply builds and signs URLs. Further
18
+ development may include adapters to various transport mechanisms and
19
+ interpretation of results.
20
+ }
21
+ s.email = %q{ot40ddj02@sneakemail.com}
22
+ s.extra_rdoc_files = [
23
+ "LICENSE.txt",
24
+ "README.rdoc"
25
+ ]
26
+ s.files = [
27
+ ".document",
28
+ "Gemfile",
29
+ "Gemfile.lock",
30
+ "LICENSE.txt",
31
+ "README.rdoc",
32
+ "Rakefile",
33
+ "VERSION",
34
+ "lib/rubizon.rb",
35
+ "lib/rubizon/abstract_sig2_product.rb",
36
+ "lib/rubizon/exceptions.rb",
37
+ "lib/rubizon/product/product_advertising.rb",
38
+ "lib/rubizon/product/sns.rb",
39
+ "lib/rubizon/request.rb",
40
+ "lib/rubizon/security_credentials.rb",
41
+ "rubizon.gemspec",
42
+ "test/helper.rb",
43
+ "test/test_abstract_sig2_product.rb",
44
+ "test/test_request.rb",
45
+ "test/test_security_credentials.rb",
46
+ "test/test_signature_sample.rb",
47
+ "test/test_sns.rb"
48
+ ]
49
+ s.homepage = %q{http://github.com/randymized/rubizon}
50
+ s.licenses = ["MIT"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = %q{1.3.7}
53
+ s.summary = %q{A Ruby interface to Amazon Web Services}
54
+ s.test_files = [
55
+ "test/helper.rb",
56
+ "test/test_abstract_sig2_product.rb",
57
+ "test/test_request.rb",
58
+ "test/test_security_credentials.rb",
59
+ "test/test_signature_sample.rb",
60
+ "test/test_sns.rb"
61
+ ]
62
+
63
+ if s.respond_to? :specification_version then
64
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
65
+ s.specification_version = 3
66
+
67
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
68
+ s.add_runtime_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
69
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
70
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
71
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
72
+ s.add_development_dependency(%q<rcov>, [">= 0"])
73
+ s.add_runtime_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
74
+ else
75
+ s.add_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
76
+ s.add_dependency(%q<shoulda>, [">= 0"])
77
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
79
+ s.add_dependency(%q<rcov>, [">= 0"])
80
+ s.add_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
81
+ end
82
+ else
83
+ s.add_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
84
+ s.add_dependency(%q<shoulda>, [">= 0"])
85
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
86
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
87
+ s.add_dependency(%q<rcov>, [">= 0"])
88
+ s.add_dependency(%q<ruby-hmac>, ["~> 0.4.0"])
89
+ end
90
+ end
91
+
data/test/helper.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'rubizon'
16
+
17
+ #access and secret ids are from the example REST requests at
18
+ #http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?rest-signature.html
19
+ AWSAccessKeyId='00000000000000000000'
20
+ SecretAccessKeyId= '1234567890'
21
+
22
+ class Test::Unit::TestCase
23
+ end
@@ -0,0 +1,75 @@
1
+ require 'helper'
2
+
3
+ class TestAbstractSig2Product < Test::Unit::TestCase
4
+ context "An AbstractSig2Product instance" do
5
+ setup do
6
+ @region= 'us-east-1'
7
+ @product= 'sns'
8
+ @arn= "arn:aws:#{@product}:#{@region}:123456789:My-Topic" #arn:aws:sns:us-east-1:123456789:My-Topic
9
+ end
10
+ should "calculate the hostname if only an ARN is specified" do
11
+ # If the ARN is: arn:aws:sns:us-east-1:123456789:My-Topic
12
+ # the host would be: sns.us-east-1.amazonaws.com
13
+ # The host may be specified specifically if it cannot be deduced from the ARN in this way
14
+ prod= Rubizon::AbstractSig2Product.new(:arn=>@arn)
15
+ assert_equal "#{@product}.#{@region}.amazonaws.com", prod.host
16
+ end
17
+ should "return any specific host name, even if a different one might be calculated from a specified ARN" do
18
+ prod= Rubizon::AbstractSig2Product.new(:arn=>@arn, :host=>'foo.com')
19
+ assert_equal 'foo.com', prod.host
20
+ end
21
+ should "determine the host if given a URL" do
22
+ prod= Rubizon::AbstractSig2Product.new(:url=>'http://foo.com/')
23
+ assert_equal 'foo.com', prod.host
24
+ end
25
+ should "determine the scheme if given a URL" do
26
+ prod= Rubizon::AbstractSig2Product.new(:url=>'ftp://foo.com/')
27
+ assert_equal 'ftp', prod.scheme
28
+ end
29
+ should "determine the path if given a URL" do
30
+ prod= Rubizon::AbstractSig2Product.new(:url=>'http://foo.com/bar')
31
+ assert_equal '/bar', prod.path
32
+ end
33
+ should "calculate the URL if only an ARN is specified" do
34
+ # If the ARN is: arn:aws:sns:us-east-1:123456789:My-Topic
35
+ # the URL would be: https://sns.us-east-1.amazonaws.com/
36
+ # The host may be specified specifically if it cannot be deduced from the ARN in this way
37
+ prod= Rubizon::AbstractSig2Product.new(:arn=>@arn)
38
+ assert_equal "https://#{@product}.#{@region}.amazonaws.com/", prod.endpoint
39
+ end
40
+ should "calculate the URL if only an ARN and a scheme is specified" do
41
+ prod= Rubizon::AbstractSig2Product.new(:arn=>@arn,:scheme=>:http)
42
+ assert_equal "http://#{@product}.#{@region}.amazonaws.com/", prod.endpoint
43
+ end
44
+ should "calculate the URL if only host is specified" do
45
+ prod= Rubizon::AbstractSig2Product.new(:host=>'example.com')
46
+ assert_equal "https://example.com/", prod.endpoint
47
+ end
48
+ should "prefer a specified host over one built from ARN" do
49
+ prod= Rubizon::AbstractSig2Product.new(:host=>'foo.com',:scheme=>'http',:arn=>@arn)
50
+ assert_equal 'http://foo.com/', prod.endpoint
51
+ end
52
+ should "return any specified path" do
53
+ prod= Rubizon::AbstractSig2Product.new(:host=>'x.com',:path=>'abc/xyz')
54
+ assert_equal 'abc/xyz', prod.path
55
+ end
56
+ should "append any specified path to a URL generated from an ARN" do
57
+ prod= Rubizon::AbstractSig2Product.new(:arn=>@arn, :path=>'/abc/xyz')
58
+ assert_equal "https://#{@product}.#{@region}.amazonaws.com/abc/xyz", prod.endpoint
59
+ end
60
+ should "return query elements not related to specifying the endpoint in the query_elements hash" do
61
+ prod= Rubizon::AbstractSig2Product.new(
62
+ 'arn'=>@arn,
63
+ 'scheme'=>'http',
64
+ 'host'=>'foo.com',
65
+ 'path'=>'/abc/xyz',
66
+ 'foo'=>'bar'
67
+ )
68
+ assert_equal 4, prod.query_elements.size
69
+ assert_equal 'bar', prod.query_elements['foo']
70
+ assert_equal 'HmacSHA256', prod.query_elements['SignatureMethod']
71
+ assert_equal 2, prod.query_elements['SignatureVersion']
72
+ end
73
+ end
74
+ end
75
+
@@ -0,0 +1,87 @@
1
+ require 'helper'
2
+ require 'rubizon/abstract_sig2_product' # this would normally be required by the specific product's implementation file
3
+
4
+ class TestRequest < Test::Unit::TestCase
5
+ @@credentials= Rubizon::SecurityCredentials.new('00000000000000000000','1234567890')
6
+ @@eCommerceServiceProduct= Rubizon::AbstractSig2Product.new(
7
+ :scheme=>'http',
8
+ :host=>'webservices.amazon.com',
9
+ :path=>'/onca/xml',
10
+ '_omit' => ['SignatureMethod','SignatureVersion']
11
+ )
12
+ @@eCommerceServiceRequestElements= {
13
+ 'Service'=>'AWSECommerceService',
14
+ 'Operation'=>'ItemLookup',
15
+ 'ItemId'=>'0679722769',
16
+ 'ResponseGroup'=>'ItemAttributes,Offers,Images,Reviews',
17
+ 'Version'=>'2009-01-06',
18
+ 'Timestamp'=>'2009-01-01T12:00:00Z'
19
+ }
20
+ @@expectedSignature= 'Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D'
21
+ context "A Request instance" do
22
+ setup do
23
+ @region= 'us-east-1'
24
+ @product= 'sns'
25
+ @arn= "arn:aws:#{@product}:#{@region}:123456789:My-Topic" #arn:aws:sns:us-east-1:123456789:My-Topic
26
+ @host= "#{@product}.#{@region}.amazonaws.com"
27
+ end
28
+ should "report the product's host" do
29
+ prod= Rubizon::AbstractSig2Product.new(
30
+ :arn=>@arn
31
+ )
32
+ req= prod.create_request(@@credentials)
33
+ assert_equal @host, req.host
34
+ end
35
+ should "report the product's scheme" do
36
+ prod= Rubizon::AbstractSig2Product.new(
37
+ :arn=>@arn
38
+ )
39
+ req= prod.create_request(@@credentials)
40
+ assert_equal 'https', req.scheme
41
+ end
42
+ should "report the product's path" do
43
+ prod= Rubizon::AbstractSig2Product.new(
44
+ :arn=>@arn
45
+ )
46
+ req= prod.create_request(@@credentials)
47
+ assert_equal '/', req.path
48
+ end
49
+ should "report the product's endpoint" do
50
+ prod= Rubizon::AbstractSig2Product.new(
51
+ :arn=>@arn
52
+ )
53
+ req= prod.create_request(@@credentials)
54
+ assert_equal 'https://sns.us-east-1.amazonaws.com/', req.endpoint
55
+ end
56
+ should "Create a URL that contains the expected host name for the sample request" do
57
+ req= @@eCommerceServiceProduct.create_request(@@credentials)
58
+ req.add_query_elements @@eCommerceServiceRequestElements
59
+ uri = URI.parse(req.url);
60
+ assert_equal @@eCommerceServiceProduct.host, uri.host
61
+ end
62
+ should "Create a URL that contains the expected path for the sample request" do
63
+ req= @@eCommerceServiceProduct.create_request(@@credentials)
64
+ req.add_query_elements @@eCommerceServiceRequestElements
65
+ uri = URI.parse(req.url);
66
+ assert_equal @@eCommerceServiceProduct.path, uri.path
67
+ end
68
+ should "Create a URL that contains the expected scheme for the sample request" do
69
+ req= @@eCommerceServiceProduct.create_request(@@credentials)
70
+ req.add_query_elements @@eCommerceServiceRequestElements
71
+ uri = URI.parse(req.url);
72
+ assert_equal @@eCommerceServiceProduct.scheme, uri.scheme
73
+ end
74
+ should "Create a URL that contains the expected query string for the sample request" do
75
+ req= @@eCommerceServiceProduct.create_request(@@credentials)
76
+ req.add_query_elements @@eCommerceServiceRequestElements
77
+ uri = URI.parse(req.url);
78
+ query= CGI::parse(uri.query)
79
+ @@eCommerceServiceRequestElements.each do |k,v|
80
+ assert_equal query.delete(k).first, v
81
+ end
82
+ assert_equal query.delete('AWSAccessKeyId').first, @@credentials.accessID
83
+ assert_equal CGI::escape(query.delete('Signature').first), @@expectedSignature
84
+ assert query.empty? #there's nothing left! All elements are accounted for
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ class TestSecurityCredentials < Test::Unit::TestCase
4
+ context "A SecurityCredentials instance" do
5
+ setup do
6
+ @id= Rubizon::SecurityCredentials.new(AWSAccessKeyId,SecretAccessKeyId)
7
+ @arbitrary_string= 'x'
8
+ @expected_signature= '6KClHg2k6AiXNRwaLa7sC7LIxP4NkUieZheem0eHnBI='
9
+ end
10
+ should "report the access key ID" do
11
+ assert_equal AWSAccessKeyId, @id.accessID
12
+ end
13
+ should "not allow accessing the secret access key ID" do
14
+ assert !@id.respond_to?(:secretID)
15
+ assert !@id.respond_to?(:secretKeyID)
16
+ assert !@id.respond_to?(:secretAccessKeyID)
17
+ assert !@id.respond_to?(:awsSecretAccessKeyID)
18
+ end
19
+ should "sign an arbitrary string using the secretID" do
20
+ assert_equal @expected_signature, @id.sign256(@arbitrary_string)
21
+ end
22
+ should "produce a different signature if initialized with a different access key" do
23
+ assert_not_equal @expected_signature, Rubizon::SecurityCredentials.new(AWSAccessKeyId,SecretAccessKeyId+'x').sign256(@arbitrary_string)
24
+ end
25
+ should "sign an arbitrary string based upon a signature method of HmacSHA256" do
26
+ assert_equal @expected_signature, @id.sign('HmacSHA256',@arbitrary_string)
27
+ end
28
+ should "raise an exception if an unsupported signature method is requested" do
29
+ assert_raise(Rubizon::UnsupportedSignatureMethodError) do
30
+ @id.sign('foo',@arbitrary_string)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,66 @@
1
+ require 'helper'
2
+ require 'rubizon/product/product_advertising'
3
+
4
+ # This is a special test based upon a signature generation sample at
5
+ # http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?rest-signature.html
6
+ # That is the only sample I know of where an actual signature, based upon
7
+ # simulated security credentials and request parameters is documented.
8
+
9
+ # This also serves as a test of the integration of security credentials,
10
+ # Actions, Products and Requests in order to generate a properly formed URL.
11
+
12
+ class TestSignatureSample < Test::Unit::TestCase
13
+ @@credentials= Rubizon::SecurityCredentials.new('00000000000000000000','1234567890')
14
+ @@eCommerceServiceProduct= Rubizon::ProductAdvertisingProduct.new(@@credentials)
15
+ @@eCommerceServiceRequestSubject= {
16
+ 'ItemId'=>'0679722769',
17
+ 'ResponseGroup'=>'ItemAttributes,Offers,Images,Reviews',
18
+ 'Version'=>'2009-01-06',
19
+ 'Timestamp'=>'2009-01-01T12:00:00Z'
20
+ }
21
+ @@expectedSignature= 'Nace%2BU3Az4OhN7tISqgs1vdLBHBEijWcBeCqL5xN9xg%3D'
22
+ context "The signature generation example" do
23
+ should "calculate the signature (version 2) expected in the example" do
24
+ req= @@eCommerceServiceProduct.item_lookup_request(@@eCommerceServiceRequestSubject)
25
+ q= req.query_string
26
+ assert_equal <<____.rstrip, req.canonical_querystring
27
+ AWSAccessKeyId=00000000000000000000&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06
28
+ ____
29
+ assert_equal <<____.rstrip, req.string_to_sign
30
+ GET
31
+ webservices.amazon.com
32
+ /onca/xml
33
+ AWSAccessKeyId=00000000000000000000&ItemId=0679722769&Operation=ItemLookup&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&Service=AWSECommerceService&Timestamp=2009-01-01T12%3A00%3A00Z&Version=2009-01-06
34
+ ____
35
+ assert_equal @@expectedSignature, CGI::escape(CGI::parse(q)['Signature'].first)
36
+ end
37
+ should "Create a URL that contains the expected host name for the sample request" do
38
+ req= @@eCommerceServiceProduct.item_lookup_request(@@eCommerceServiceRequestSubject)
39
+ uri = URI.parse(req.url);
40
+ assert_equal @@eCommerceServiceProduct.host, uri.host
41
+ end
42
+ should "Create a URL that contains the expected path for the sample request" do
43
+ req= @@eCommerceServiceProduct.item_lookup_request(@@eCommerceServiceRequestSubject)
44
+ uri = URI.parse(req.url);
45
+ assert_equal @@eCommerceServiceProduct.path, uri.path
46
+ end
47
+ should "Create a URL that contains the expected scheme for the sample request" do
48
+ req= @@eCommerceServiceProduct.item_lookup_request(@@eCommerceServiceRequestSubject)
49
+ uri = URI.parse(req.url);
50
+ assert_equal @@eCommerceServiceProduct.scheme, uri.scheme
51
+ end
52
+ should "Create a URL that contains the expected query string for the sample request" do
53
+ req= @@eCommerceServiceProduct.item_lookup_request(@@eCommerceServiceRequestSubject)
54
+ uri = URI.parse(req.url);
55
+ query= CGI::parse(uri.query)
56
+ @@eCommerceServiceRequestSubject.each do |k,v|
57
+ assert_equal query.delete(k).first, v
58
+ end
59
+ assert_equal 'ItemLookup', CGI::escape(query.delete('Operation').first)
60
+ assert_equal 'AWSECommerceService', CGI::escape(query.delete('Service').first)
61
+ assert_equal @@credentials.accessID, query.delete('AWSAccessKeyId').first
62
+ assert_equal @@expectedSignature, CGI::escape(query.delete('Signature').first)
63
+ assert query.empty? #there's nothing left! All elements are accounted for
64
+ end
65
+ end
66
+ end
data/test/test_sns.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'helper'
2
+ require 'rubizon/product/sns'
3
+
4
+ class TestSNS < Test::Unit::TestCase
5
+ @@access_key= '00000000000000000000'
6
+ @@credentials= Rubizon::SecurityCredentials.new(@@access_key,'1234567890')
7
+ @@arn= 'arn:aws:sns:us-east-1:123456789:My-Topic'
8
+ @@host= 'sns.us-east-1.amazonaws.com'
9
+ @@snsProduct= Rubizon::SimpleNotificationService.new(@@credentials,@@host)
10
+ context "A SimpleNotificationService instance" do
11
+ should "formulate a url that will publish a message to a topic" do
12
+ message= 'hello world'
13
+ req= @@snsProduct.topic(@@arn).publish(message)
14
+ assert_equal "http://#{@@host}/", req.endpoint
15
+ q= CGI::parse(req.query_string)
16
+ assert_equal '2', q['SignatureVersion'].first
17
+ assert_equal 'HmacSHA256', q['SignatureMethod'].first
18
+ assert q['Signature'].first.is_a?(String)
19
+ assert_equal 44, CGI::unescape(q['Signature'].first).length
20
+ assert_equal @@access_key, q['AWSAccessKeyId'].first
21
+ assert q.has_key?('Timestamp')
22
+ assert_equal 'Publish', q['Action'].first
23
+ assert_equal @@arn, CGI::unescape(q['TopicArn'].first)
24
+ assert_equal message, CGI::unescape(q['Message'].first)
25
+ assert !q.has_key?('Subject')
26
+ end
27
+ should "formulate a url that will publish a message and a subject to a topic" do
28
+ message= 'world'
29
+ subject= 'An important word'
30
+ req= @@snsProduct.topic(@@arn).publish(message,subject)
31
+ q= CGI::parse(req.query_string)
32
+ assert_equal 'Publish', q['Action'].first
33
+ assert_equal @@arn, CGI::unescape(q['TopicArn'].first)
34
+ assert_equal message, CGI::unescape(q['Message'].first)
35
+ assert_equal subject, CGI::unescape(q['Subject'].first)
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,184 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubizon
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Randy McLaughlin
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-03 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ruby-hmac
22
+ type: :runtime
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 4
31
+ - 0
32
+ version: 0.4.0
33
+ prerelease: false
34
+ requirement: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: shoulda
37
+ type: :development
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ prerelease: false
47
+ requirement: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ type: :development
51
+ version_requirements: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 1
58
+ - 0
59
+ - 0
60
+ version: 1.0.0
61
+ prerelease: false
62
+ requirement: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: jeweler
65
+ type: :development
66
+ version_requirements: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 1
73
+ - 5
74
+ - 1
75
+ version: 1.5.1
76
+ prerelease: false
77
+ requirement: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: rcov
80
+ type: :development
81
+ version_requirements: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ prerelease: false
90
+ requirement: *id005
91
+ - !ruby/object:Gem::Dependency
92
+ name: ruby-hmac
93
+ type: :runtime
94
+ version_requirements: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ - 4
102
+ - 0
103
+ version: 0.4.0
104
+ prerelease: false
105
+ requirement: *id006
106
+ description: |
107
+ A Ruby interface to Amazon Web Services. Rubizon separates creating a
108
+ properly-formed, signed URL for making an AWS request from the transport
109
+ mechanism used.
110
+
111
+ In its initial implementation, Rubizon simply builds and signs URLs. Further
112
+ development may include adapters to various transport mechanisms and
113
+ interpretation of results.
114
+
115
+ email: ot40ddj02@sneakemail.com
116
+ executables: []
117
+
118
+ extensions: []
119
+
120
+ extra_rdoc_files:
121
+ - LICENSE.txt
122
+ - README.rdoc
123
+ files:
124
+ - .document
125
+ - Gemfile
126
+ - Gemfile.lock
127
+ - LICENSE.txt
128
+ - README.rdoc
129
+ - Rakefile
130
+ - VERSION
131
+ - lib/rubizon.rb
132
+ - lib/rubizon/abstract_sig2_product.rb
133
+ - lib/rubizon/exceptions.rb
134
+ - lib/rubizon/product/product_advertising.rb
135
+ - lib/rubizon/product/sns.rb
136
+ - lib/rubizon/request.rb
137
+ - lib/rubizon/security_credentials.rb
138
+ - rubizon.gemspec
139
+ - test/helper.rb
140
+ - test/test_abstract_sig2_product.rb
141
+ - test/test_request.rb
142
+ - test/test_security_credentials.rb
143
+ - test/test_signature_sample.rb
144
+ - test/test_sns.rb
145
+ has_rdoc: true
146
+ homepage: http://github.com/randymized/rubizon
147
+ licenses:
148
+ - MIT
149
+ post_install_message:
150
+ rdoc_options: []
151
+
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ hash: 3
160
+ segments:
161
+ - 0
162
+ version: "0"
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ segments:
169
+ - 0
170
+ version: "0"
171
+ requirements: []
172
+
173
+ rubyforge_project:
174
+ rubygems_version: 1.3.7
175
+ signing_key:
176
+ specification_version: 3
177
+ summary: A Ruby interface to Amazon Web Services
178
+ test_files:
179
+ - test/helper.rb
180
+ - test/test_abstract_sig2_product.rb
181
+ - test/test_request.rb
182
+ - test/test_security_credentials.rb
183
+ - test/test_signature_sample.rb
184
+ - test/test_sns.rb