stormmq-client 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ require 'singleton'
10
+ require 'json'
11
+ require 'base64'
12
+ require 'stormmq/base64_extensions'
13
+ require 'stormmq/errors'
14
+
15
+ module StormMQ
16
+
17
+ SECRET_KEYS_SEARCH_PATH = ['~/.stormmq','/etc']
18
+ SECRET_KEYS_FILENAME = 'secret-keys.json'
19
+
20
+ class SecretKeys
21
+ include Singleton
22
+ attr_writer :key_cache
23
+
24
+ # Returns the base64 encoded secret key for the given user name from the secret keys file.
25
+ def key_for(user)
26
+ raise Error::UserNotProvidedError, "user cannot be nil." if user.nil?
27
+ raise Error::UserNotProvidedError, "user cannot be empty." if user.empty?
28
+ keys[user] || (raise Error::SecretKeyNotFoundError, "a secret key for user '#{user}' could not be found in the secret key file.", caller)
29
+ end
30
+
31
+ # Returns an <tt>Array</tt> of user names of users who have keys in the secret keys file.
32
+ def users
33
+ keys.keys
34
+ end
35
+
36
+ private
37
+
38
+ # Return a hash of keys stored in the secret keys file.
39
+ def keys
40
+ @secret_keys_cache ||= load_secret_keys
41
+ end
42
+
43
+ # Load the keys from the secret keys file <tt>keyfile</tt>. Walks the locations specified in
44
+ # <tt>search_path</tt> in order of preference.
45
+ def load_secret_keys(search_path=SECRET_KEYS_SEARCH_PATH, keyfile=SECRET_KEYS_FILENAME)
46
+ full_paths = search_path.map{|p| File.expand_path("#{p}/#{keyfile}")}
47
+ full_paths.each do |full_path|
48
+ begin
49
+ return SecretKeys.secret_keys_hash_from_json(IO.read(full_path))
50
+ rescue
51
+ end
52
+ end
53
+ raise Error::LoadSecretKeysError,
54
+ "Could not read the secret keys file from any of [#{full_paths.join ', '}]. Please ensure that a valid keyfile exists in one of these locations and that it is readable.",
55
+ caller
56
+ end
57
+
58
+ def self.key_for(*args)
59
+ self.instance.key_for(*args)
60
+ end
61
+
62
+ # Create a hash of the keys contained in json fragment.
63
+ def self.secret_keys_hash_from_json(json_string)
64
+ secret_keys_hash = JSON.parse(json_string)
65
+ secret_keys_hash.inject({}) { |h,(k,v)| h[k] = Base64.urlsafe_decode64(v); h }
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,125 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ require 'uri'
10
+ require 'cgi'
11
+ require 'hmac'
12
+ require 'hmac-sha2'
13
+ require 'base64'
14
+
15
+ require 'stormmq/errors'
16
+
17
+ module StormMQ
18
+
19
+ SCHEMES_DEFAULT_PORTS = {
20
+ 'http' => 80,
21
+ 'https' => 443
22
+ }
23
+
24
+ class URL
25
+
26
+ def initialize(url)
27
+ if url.is_a?(Hash)
28
+ @url = URI::Generic.build(url)
29
+ else
30
+ @url = URI.parse(url)
31
+ end
32
+ raise Error::InvalidURLError, "'#{@url.to_s}' is not a valid URL." unless self.valid?
33
+ end
34
+
35
+ def valid?
36
+ begin
37
+ URI.parse(@url.to_s).class != URI::Generic
38
+ rescue URI::InvalidURIError
39
+ false
40
+ end
41
+ end
42
+
43
+ def to_s
44
+ @url.to_s
45
+ end
46
+
47
+ def add_query_params(params_hash)
48
+ components = to_h
49
+ query_hash = StormMQ::URL.querystring_to_hash(components[:query])
50
+ components[:query] = StormMQ::URL.hash_to_canonical_querystring(
51
+ query_hash.merge(params_hash)
52
+ )
53
+ self.class.new(components)
54
+ end
55
+
56
+ def add_user_and_version_query_params(user, version=0)
57
+ add_query_params(:user => [user], :version => [version.to_s])
58
+ end
59
+
60
+ def canonicalise(user, version=0)
61
+ components = self.add_user_and_version_query_params(user, version).to_h
62
+ components[:query] = StormMQ::URL.canonicalise_query_string(components[:query]) unless components[:query].nil?
63
+ canonical = { :port => StormMQ::URL.default_port_for_scheme(components[:scheme]) }.merge(components)
64
+ self.class.new(canonical)
65
+ end
66
+
67
+ def sign(secret_key, method='GET')
68
+ self.add_query_params('signature' => compute_signature(secret_key, method))
69
+ end
70
+
71
+ def compute_signature(secret_key, method='GET')
72
+ hmac = HMAC::SHA256.new(secret_key)
73
+ hmac.update("#{method.upcase}#{self.to_s}")
74
+ Base64.urlsafe_encode64(hmac.digest).chomp
75
+ end
76
+
77
+ def canonicalise_and_sign(user, secret_key, method='GET', verison=0)
78
+ self.canonicalise(user,verison).sign(secret_key, method)
79
+ end
80
+
81
+ def to_h
82
+ components = URI.split(@url.to_s)
83
+ {
84
+ :scheme => components[0],
85
+ :userinfo => components[1],
86
+ :host => components[2],
87
+ :port => components[3],
88
+ :registry => components[4],
89
+ :path => components[5],
90
+ :opaque => components[6],
91
+ :query => components[7],
92
+ :fragment => components[8]
93
+ }.reject {|k,v| v.nil?}
94
+ end
95
+
96
+ def self.escape(string)
97
+ return '' if string.nil?
98
+ URI.escape(string, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
99
+ end
100
+
101
+ def self.default_port_for_scheme(scheme)
102
+ SCHEMES_DEFAULT_PORTS[scheme.downcase]
103
+ end
104
+
105
+ def self.querystring_to_hash(querystring)
106
+ return {} if querystring.nil?
107
+ CGI.parse(querystring)
108
+ end
109
+
110
+ def self.hash_to_canonical_querystring(options)
111
+ components = []
112
+ options.each do |key,values|
113
+ [values].flatten.each {|v| components << "#{StormMQ::URL.escape(key.to_s)}=#{StormMQ::URL.escape(v.to_s)}" }
114
+ end
115
+ components.sort.join('&')
116
+ end
117
+
118
+ def self.canonicalise_query_string(querystring)
119
+ query_hash = StormMQ::URL.querystring_to_hash(querystring)
120
+ StormMQ::URL.hash_to_canonical_querystring(query_hash)
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,27 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ module StormMQ
10
+ module Utils
11
+ # def self.stormmq_credentials_string(user, password)
12
+ # "\0#{user}\0#{password}"
13
+ # end
14
+ end
15
+ end
16
+
17
+ class String
18
+ def blank?
19
+ self.gsub(/\s+/,'') == ""
20
+ end
21
+ end
22
+
23
+ class NilClass
24
+ def blank?
25
+ true
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ require File.dirname(__FILE__) + '/../../lib/stormmq/amqp'
10
+
11
+ describe StormMQ::AMQPClient do
12
+ it "constructs the virtual host string from the StormMQ specific options in the connect option hash" do
13
+ options = {
14
+ :company => 'a_company',
15
+ :system => 'a_system',
16
+ :environment => 'an_environment'
17
+ }
18
+ StormMQ::AMQPClient.vhost_from_options(options).should == '/a_company/a_system/an_environment'
19
+ end
20
+ end
21
+
@@ -0,0 +1,25 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ require 'base64'
10
+ require File.dirname(__FILE__) + '/../../lib/stormmq/secret_keys'
11
+ require File.dirname(__FILE__) + '/../../lib/stormmq/base64_extensions'
12
+
13
+ describe StormMQ::SecretKeys do
14
+
15
+ describe "secret_keys_hash_from_json" do
16
+
17
+ it "should load the secret key for a user from the json format string" do
18
+ url_safe_base64_key = "BNuWk1agaAUPTZ15sx44kHvNkTnJXsevqTjIo1M1iwFOeNaUqr3qP-_5Dnk=="
19
+ json = %Q|{"tonybyrne":"#{url_safe_base64_key}"}|
20
+ StormMQ::SecretKeys.secret_keys_hash_from_json(json).should == { 'tonybyrne' => Base64.urlsafe_decode64(url_safe_base64_key) }
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,129 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ require File.dirname(__FILE__) + '/../../lib/stormmq/url'
10
+
11
+ describe StormMQ::URL do
12
+
13
+ describe "to_s" do
14
+
15
+ it "should return the url as a string" do
16
+ StormMQ::URL.new('https://api.stormmq.com/').to_s.should == 'https://api.stormmq.com/'
17
+ end
18
+
19
+ end
20
+
21
+ describe "canonicalise" do
22
+
23
+ it "adds explicit port for HTTP" do
24
+ StormMQ::URL.new('http://api.stormmq.com/').canonicalise('test').to_s.should == 'http://api.stormmq.com:80/?user=test&version=0'
25
+ end
26
+
27
+ it "adds explicit port for HTTPS" do
28
+ StormMQ::URL.new('https://api.stormmq.com/').canonicalise('test').to_s.should == 'https://api.stormmq.com:443/?user=test&version=0'
29
+ end
30
+
31
+ it "sorts query string params by param name" do
32
+ StormMQ::URL.new('https://api.stormmq.com/?z=3&x=1&y=2').canonicalise('test').to_s.should == 'https://api.stormmq.com:443/?user=test&version=0&x=1&y=2&z=3'
33
+ end
34
+
35
+ it "sorts query string params with multiple values by value" do
36
+ StormMQ::URL.new('https://api.stormmq.com/?z=3&x=1&y=2&z=1').canonicalise('test').to_s.should == 'https://api.stormmq.com:443/?user=test&version=0&x=1&y=2&z=1&z=3'
37
+ end
38
+
39
+ it "should canonicalise complex example from http://stormmq.com/rest-apis/for-security-reasons (without user and version params)" do
40
+ url = 'https://api.stormmq.com/api/2009-01-01/%3D%25?empty=&%20novalue&foo=%2Fvalue'
41
+ expected = 'https://api.stormmq.com:443/api/2009-01-01/%3D%25?%20novalue=&empty=&foo=%2Fvalue&user=raph&version=0'
42
+ StormMQ::URL.new(url).canonicalise('raph').to_s.should == expected
43
+ end
44
+
45
+ end
46
+
47
+ describe "add_query_params" do
48
+
49
+ before(:each) do
50
+ @url = StormMQ::URL.new('http://api.stormmq.com/')
51
+ end
52
+
53
+ it "should add params to the querystring and canonicalise the querystring" do
54
+ @url.add_query_params('z' => 3, 'x' => 1, 'y' => 2).to_s.should == 'http://api.stormmq.com/?x=1&y=2&z=3'
55
+ end
56
+
57
+ it "should add params as symbols to the querystring and canonicalise the querystring" do
58
+ @url.add_query_params(:z => 3, :x => 1, :y => 2).to_s.should == 'http://api.stormmq.com/?x=1&y=2&z=3'
59
+ end
60
+
61
+ it "should add params with multiple values to the querystring and canonicalise the querystring" do
62
+ @url.add_query_params(:z => [3,1,2]).to_s.should == 'http://api.stormmq.com/?z=1&z=2&z=3'
63
+ end
64
+
65
+ it "should URI escape params and values added to a URL" do
66
+ @url.add_query_params(' test ' => 'a+value').to_s.should == 'http://api.stormmq.com/?%20test%20=a%2Bvalue'
67
+ end
68
+
69
+ end
70
+
71
+ describe "add_user_and_version_query_params" do
72
+
73
+ before(:each) do
74
+ @url = StormMQ::URL.new('http://api.stormmq.com/')
75
+ end
76
+
77
+ it "should add the user and version querystring params to a URL" do
78
+ @url.add_user_and_version_query_params('tonybyrne',1).to_s.should == 'http://api.stormmq.com/?user=tonybyrne&version=1'
79
+ end
80
+
81
+ it "version should default to '0'" do
82
+ @url.add_user_and_version_query_params('tonybyrne').to_s.should == 'http://api.stormmq.com/?user=tonybyrne&version=0'
83
+ end
84
+
85
+ end
86
+
87
+ describe "query_string_to_hash" do
88
+
89
+ it "should convert a simple query string to a hash representation" do
90
+ StormMQ::URL.querystring_to_hash("param=value").should == { 'param' => ['value'] }
91
+ end
92
+
93
+ it "should uri decode the query string" do
94
+ StormMQ::URL.querystring_to_hash("%20param=value%20").should == { ' param' => ['value '] }
95
+ end
96
+
97
+ end
98
+
99
+ describe "hash_to_canonical_querystring" do
100
+
101
+ it "should convert a hash with a single key value pair to a query string" do
102
+ StormMQ::URL.hash_to_canonical_querystring({'param' => ['value']}).should == "param=value"
103
+ end
104
+
105
+ it "should uri encode components of query string" do
106
+ StormMQ::URL.hash_to_canonical_querystring(
107
+ {
108
+ 'param with spaces' => ['value with spaces']
109
+ }
110
+ ).should == "param%20with%20spaces=value%20with%20spaces"
111
+ end
112
+
113
+ it "should convert a hash with a single key, but multiple values to a query string, and sort the components" do
114
+ StormMQ::URL.hash_to_canonical_querystring({'param' => ['Z','X','Y']}).should == "param=X&param=Y&param=Z"
115
+ end
116
+
117
+ it "should convert a hash with a multiple keys, and multiple values to a query string, and sort the components" do
118
+ StormMQ::URL.hash_to_canonical_querystring(
119
+ {
120
+ 'paramZ' => ['Z','X','Y'],
121
+ 'paramX' => ['1','3','2'],
122
+ 'paramY' => ['c','a','b']
123
+ }
124
+ ).should == "paramX=1&paramX=2&paramX=3&paramY=a&paramY=b&paramY=c&paramZ=X&paramZ=Y&paramZ=Z"
125
+ end
126
+
127
+ end
128
+
129
+ end
@@ -0,0 +1,13 @@
1
+ #--
2
+ # Copyright (c) 2010, Tony Byrne & StormMQ Ltd.
3
+ # All rights reserved.
4
+ #
5
+ # Please refer to the LICENSE file that accompanies this source
6
+ # for terms of use and redistribution.
7
+ #++
8
+
9
+ require File.dirname(__FILE__) + '/../../lib/stormmq/utils'
10
+
11
+ describe StormMQ::Utils do
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,254 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stormmq-client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ version: 0.0.4
11
+ platform: ruby
12
+ authors:
13
+ - Tony Byrne
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain:
17
+ date: 2010-06-07 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: amqp
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 9
29
+ segments:
30
+ - 0
31
+ - 6
32
+ - 7
33
+ version: 0.6.7
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rest-client
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 1
47
+ - 4
48
+ - 2
49
+ version: 1.4.2
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: ruby-hmac
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 15
61
+ segments:
62
+ - 0
63
+ - 4
64
+ - 0
65
+ version: 0.4.0
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: json
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 1
79
+ - 4
80
+ - 2
81
+ version: 1.4.2
82
+ type: :runtime
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ name: commandline
86
+ prerelease: false
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 23
93
+ segments:
94
+ - 0
95
+ - 7
96
+ - 10
97
+ version: 0.7.10
98
+ type: :runtime
99
+ version_requirements: *id005
100
+ - !ruby/object:Gem::Dependency
101
+ name: rspec
102
+ prerelease: false
103
+ requirement: &id006 !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 27
109
+ segments:
110
+ - 1
111
+ - 3
112
+ - 0
113
+ version: 1.3.0
114
+ type: :development
115
+ version_requirements: *id006
116
+ - !ruby/object:Gem::Dependency
117
+ name: rake
118
+ prerelease: false
119
+ requirement: &id007 !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 49
125
+ segments:
126
+ - 0
127
+ - 8
128
+ - 7
129
+ version: 0.8.7
130
+ type: :development
131
+ version_requirements: *id007
132
+ - !ruby/object:Gem::Dependency
133
+ name: rcov
134
+ prerelease: false
135
+ requirement: &id008 !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 25
141
+ segments:
142
+ - 0
143
+ - 9
144
+ - 7
145
+ - 1
146
+ version: 0.9.7.1
147
+ type: :development
148
+ version_requirements: *id008
149
+ - !ruby/object:Gem::Dependency
150
+ name: rdoc
151
+ prerelease: false
152
+ requirement: &id009 !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ hash: 11
158
+ segments:
159
+ - 2
160
+ - 5
161
+ - 8
162
+ version: 2.5.8
163
+ type: :development
164
+ version_requirements: *id009
165
+ description: A client library for StormMQ's Cloud Messaging service. See http://www.stormmq.com/ for details of the service.
166
+ email: stormmq@byrnehq.com
167
+ executables:
168
+ - stormmq-amqp-echo-test
169
+ - stormmq-create-system
170
+ - stormmq-delete-system
171
+ - stormmq-describe-company
172
+ - stormmq-describe-system
173
+ - stormmq-get-amqpuser-password
174
+ - stormmq-list-amqpusers
175
+ - stormmq-list-apis
176
+ - stormmq-list-bindings
177
+ - stormmq-list-clusters
178
+ - stormmq-list-companies
179
+ - stormmq-list-exchanges
180
+ - stormmq-list-queues
181
+ - stormmq-list-systems
182
+ - stormmq-url-signer
183
+ extensions: []
184
+
185
+ extra_rdoc_files: []
186
+
187
+ files:
188
+ - lib/stormmq/amqp.rb
189
+ - lib/stormmq/application.rb
190
+ - lib/stormmq/base64_extensions.rb
191
+ - lib/stormmq/errors.rb
192
+ - lib/stormmq/rest.rb
193
+ - lib/stormmq/secret_keys.rb
194
+ - lib/stormmq/url.rb
195
+ - lib/stormmq/utils.rb
196
+ - bin/stormmq-amqp-echo-test
197
+ - bin/stormmq-create-system
198
+ - bin/stormmq-delete-system
199
+ - bin/stormmq-describe-company
200
+ - bin/stormmq-describe-system
201
+ - bin/stormmq-get-amqpuser-password
202
+ - bin/stormmq-list-amqpusers
203
+ - bin/stormmq-list-apis
204
+ - bin/stormmq-list-bindings
205
+ - bin/stormmq-list-clusters
206
+ - bin/stormmq-list-companies
207
+ - bin/stormmq-list-exchanges
208
+ - bin/stormmq-list-queues
209
+ - bin/stormmq-list-systems
210
+ - bin/stormmq-url-signer
211
+ - spec/stormmq/amqp_spec.rb
212
+ - spec/stormmq/secret_keys_spec.rb
213
+ - spec/stormmq/url_spec.rb
214
+ - spec/stormmq/utils_spec.rb
215
+ - LICENSE
216
+ - Rakefile
217
+ - README
218
+ - TODO
219
+ has_rdoc: true
220
+ homepage: http://github.com/tonybyrne/StormMQ-Ruby-Client
221
+ licenses: []
222
+
223
+ post_install_message:
224
+ rdoc_options: []
225
+
226
+ require_paths:
227
+ - lib
228
+ required_ruby_version: !ruby/object:Gem::Requirement
229
+ none: false
230
+ requirements:
231
+ - - ">="
232
+ - !ruby/object:Gem::Version
233
+ hash: 3
234
+ segments:
235
+ - 0
236
+ version: "0"
237
+ required_rubygems_version: !ruby/object:Gem::Requirement
238
+ none: false
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ hash: 3
243
+ segments:
244
+ - 0
245
+ version: "0"
246
+ requirements: []
247
+
248
+ rubyforge_project:
249
+ rubygems_version: 1.3.7
250
+ signing_key:
251
+ specification_version: 3
252
+ summary: A client library for StormMQ's Cloud Messaging service
253
+ test_files: []
254
+