scout 5.7.1 → 5.7.2.pre

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.
Files changed (251) hide show
  1. data/CHANGELOG.markdown +5 -0
  2. data/lib/scout/command/run.rb +1 -1
  3. data/lib/scout/server.rb +2 -1
  4. data/lib/scout/streamer.rb +3 -2
  5. data/lib/scout/streamer_daemon.rb +4 -4
  6. data/lib/scout/version.rb +1 -1
  7. data/test/scout_test.rb +7 -8
  8. data/vendor/httpclient/README.txt +759 -0
  9. data/vendor/httpclient/bin/httpclient +65 -0
  10. data/vendor/httpclient/lib/hexdump.rb +50 -0
  11. data/vendor/httpclient/lib/http-access2.rb +55 -0
  12. data/vendor/httpclient/lib/http-access2/cookie.rb +1 -0
  13. data/vendor/httpclient/lib/http-access2/http.rb +1 -0
  14. data/vendor/httpclient/lib/httpclient.rb +1156 -0
  15. data/vendor/httpclient/lib/httpclient/auth.rb +899 -0
  16. data/vendor/httpclient/lib/httpclient/cacert.p7s +1912 -0
  17. data/vendor/httpclient/lib/httpclient/connection.rb +88 -0
  18. data/vendor/httpclient/lib/httpclient/cookie.rb +438 -0
  19. data/vendor/httpclient/lib/httpclient/http.rb +1046 -0
  20. data/vendor/httpclient/lib/httpclient/include_client.rb +83 -0
  21. data/vendor/httpclient/lib/httpclient/session.rb +1025 -0
  22. data/vendor/httpclient/lib/httpclient/ssl_config.rb +403 -0
  23. data/vendor/httpclient/lib/httpclient/timeout.rb +140 -0
  24. data/vendor/httpclient/lib/httpclient/util.rb +178 -0
  25. data/vendor/httpclient/lib/httpclient/version.rb +3 -0
  26. data/vendor/httpclient/lib/oauthclient.rb +110 -0
  27. data/vendor/httpclient/sample/async.rb +8 -0
  28. data/vendor/httpclient/sample/auth.rb +11 -0
  29. data/vendor/httpclient/sample/cookie.rb +18 -0
  30. data/vendor/httpclient/sample/dav.rb +103 -0
  31. data/vendor/httpclient/sample/howto.rb +49 -0
  32. data/vendor/httpclient/sample/oauth_buzz.rb +57 -0
  33. data/vendor/httpclient/sample/oauth_friendfeed.rb +59 -0
  34. data/vendor/httpclient/sample/oauth_salesforce_10.rb +63 -0
  35. data/vendor/httpclient/sample/oauth_twitter.rb +61 -0
  36. data/vendor/httpclient/sample/ssl/0cert.pem +22 -0
  37. data/vendor/httpclient/sample/ssl/0key.pem +30 -0
  38. data/vendor/httpclient/sample/ssl/1000cert.pem +19 -0
  39. data/vendor/httpclient/sample/ssl/1000key.pem +18 -0
  40. data/vendor/httpclient/sample/ssl/htdocs/index.html +10 -0
  41. data/vendor/httpclient/sample/ssl/ssl_client.rb +22 -0
  42. data/vendor/httpclient/sample/ssl/webrick_httpsd.rb +29 -0
  43. data/vendor/httpclient/sample/stream.rb +21 -0
  44. data/vendor/httpclient/sample/thread.rb +27 -0
  45. data/vendor/httpclient/sample/wcat.rb +21 -0
  46. data/vendor/httpclient/test/ca-chain.cert +44 -0
  47. data/vendor/httpclient/test/ca.cert +23 -0
  48. data/vendor/httpclient/test/client.cert +19 -0
  49. data/vendor/httpclient/test/client.key +15 -0
  50. data/vendor/httpclient/test/helper.rb +129 -0
  51. data/vendor/httpclient/test/htdigest +1 -0
  52. data/vendor/httpclient/test/htpasswd +2 -0
  53. data/vendor/httpclient/test/runner.rb +2 -0
  54. data/vendor/httpclient/test/server.cert +19 -0
  55. data/vendor/httpclient/test/server.key +15 -0
  56. data/vendor/httpclient/test/sslsvr.rb +65 -0
  57. data/vendor/httpclient/test/subca.cert +21 -0
  58. data/vendor/httpclient/test/test_auth.rb +321 -0
  59. data/vendor/httpclient/test/test_cookie.rb +391 -0
  60. data/vendor/httpclient/test/test_hexdump.rb +14 -0
  61. data/vendor/httpclient/test/test_http-access2.rb +507 -0
  62. data/vendor/httpclient/test/test_httpclient.rb +1783 -0
  63. data/vendor/httpclient/test/test_include_client.rb +52 -0
  64. data/vendor/httpclient/test/test_ssl.rb +235 -0
  65. data/vendor/multi_json/.document +5 -0
  66. data/vendor/multi_json/.rspec +3 -0
  67. data/vendor/multi_json/.travis.yml +11 -0
  68. data/vendor/multi_json/.yardopts +6 -0
  69. data/vendor/multi_json/CHANGELOG.md +169 -0
  70. data/vendor/multi_json/CONTRIBUTING.md +46 -0
  71. data/vendor/multi_json/Gemfile +31 -0
  72. data/vendor/multi_json/LICENSE.md +20 -0
  73. data/vendor/multi_json/README.md +109 -0
  74. data/vendor/multi_json/Rakefile +12 -0
  75. data/vendor/multi_json/lib/multi_json.rb +157 -0
  76. data/vendor/multi_json/lib/multi_json/adapter.rb +48 -0
  77. data/vendor/multi_json/lib/multi_json/adapters/gson.rb +19 -0
  78. data/vendor/multi_json/lib/multi_json/adapters/jr_jackson.rb +19 -0
  79. data/vendor/multi_json/lib/multi_json/adapters/json_common.rb +25 -0
  80. data/vendor/multi_json/lib/multi_json/adapters/json_gem.rb +11 -0
  81. data/vendor/multi_json/lib/multi_json/adapters/json_pure.rb +11 -0
  82. data/vendor/multi_json/lib/multi_json/adapters/nsjsonserialization.rb +34 -0
  83. data/vendor/multi_json/lib/multi_json/adapters/oj.rb +24 -0
  84. data/vendor/multi_json/lib/multi_json/adapters/ok_json.rb +22 -0
  85. data/vendor/multi_json/lib/multi_json/adapters/yajl.rb +19 -0
  86. data/vendor/multi_json/lib/multi_json/convertible_hash_keys.rb +43 -0
  87. data/vendor/multi_json/lib/multi_json/load_error.rb +11 -0
  88. data/vendor/multi_json/lib/multi_json/options.rb +48 -0
  89. data/vendor/multi_json/lib/multi_json/vendor/okjson.rb +606 -0
  90. data/vendor/multi_json/lib/multi_json/version.rb +20 -0
  91. data/vendor/multi_json/multi_json.gemspec +22 -0
  92. data/vendor/multi_json/spec/adapter_shared_example.rb +235 -0
  93. data/vendor/multi_json/spec/has_options.rb +119 -0
  94. data/vendor/multi_json/spec/helper.rb +35 -0
  95. data/vendor/multi_json/spec/json_common_shared_example.rb +30 -0
  96. data/vendor/multi_json/spec/multi_json_spec.rb +226 -0
  97. data/vendor/{signature → pusher}/.document +0 -0
  98. data/vendor/{json_pure/diagrams/.keep → pusher/.gemtest} +0 -0
  99. data/vendor/pusher/.gitignore +23 -0
  100. data/vendor/pusher/.travis.yml +15 -0
  101. data/vendor/pusher/Gemfile +2 -0
  102. data/vendor/{pusher-gem → pusher}/LICENSE +1 -1
  103. data/vendor/pusher/README.md +186 -0
  104. data/vendor/{pusher-gem → pusher}/Rakefile +0 -0
  105. data/vendor/{pusher-gem → pusher}/examples/async_message.rb +0 -0
  106. data/vendor/pusher/lib/pusher.rb +60 -0
  107. data/vendor/{pusher-gem → pusher}/lib/pusher/channel.rb +47 -54
  108. data/vendor/pusher/lib/pusher/client.rb +306 -0
  109. data/vendor/pusher/lib/pusher/request.rb +107 -0
  110. data/vendor/pusher/lib/pusher/resource.rb +36 -0
  111. data/vendor/pusher/lib/pusher/webhook.rb +110 -0
  112. data/vendor/{pusher-gem → pusher}/pusher.gemspec +6 -5
  113. data/vendor/pusher/spec/channel_spec.rb +127 -0
  114. data/vendor/pusher/spec/client_spec.rb +464 -0
  115. data/vendor/{pusher-gem → pusher}/spec/spec_helper.rb +12 -0
  116. data/vendor/pusher/spec/web_hook_spec.rb +117 -0
  117. data/vendor/signature/.travis.yml +15 -0
  118. data/vendor/signature/Gemfile +1 -1
  119. data/vendor/signature/README.md +38 -28
  120. data/vendor/signature/lib/signature.rb +97 -15
  121. data/vendor/signature/lib/signature/query_encoder.rb +47 -0
  122. data/vendor/signature/lib/signature/version.rb +1 -1
  123. data/vendor/signature/signature.gemspec +3 -2
  124. data/vendor/signature/spec/signature_spec.rb +164 -55
  125. data/vendor/signature/spec/spec_helper.rb +2 -3
  126. metadata +120 -145
  127. data/vendor/json_pure/.gitignore +0 -12
  128. data/vendor/json_pure/.travis.yml +0 -20
  129. data/vendor/json_pure/CHANGES +0 -282
  130. data/vendor/json_pure/COPYING +0 -58
  131. data/vendor/json_pure/COPYING-json-jruby +0 -57
  132. data/vendor/json_pure/GPL +0 -340
  133. data/vendor/json_pure/Gemfile +0 -11
  134. data/vendor/json_pure/README-json-jruby.markdown +0 -33
  135. data/vendor/json_pure/README.rdoc +0 -358
  136. data/vendor/json_pure/Rakefile +0 -412
  137. data/vendor/json_pure/TODO +0 -1
  138. data/vendor/json_pure/VERSION +0 -1
  139. data/vendor/json_pure/data/example.json +0 -1
  140. data/vendor/json_pure/data/index.html +0 -38
  141. data/vendor/json_pure/data/prototype.js +0 -4184
  142. data/vendor/json_pure/ext/json/ext/fbuffer/fbuffer.h +0 -181
  143. data/vendor/json_pure/ext/json/ext/generator/depend +0 -1
  144. data/vendor/json_pure/ext/json/ext/generator/extconf.rb +0 -14
  145. data/vendor/json_pure/ext/json/ext/generator/generator.c +0 -1435
  146. data/vendor/json_pure/ext/json/ext/generator/generator.h +0 -148
  147. data/vendor/json_pure/ext/json/ext/parser/depend +0 -1
  148. data/vendor/json_pure/ext/json/ext/parser/extconf.rb +0 -13
  149. data/vendor/json_pure/ext/json/ext/parser/parser.c +0 -2204
  150. data/vendor/json_pure/ext/json/ext/parser/parser.h +0 -77
  151. data/vendor/json_pure/ext/json/ext/parser/parser.rl +0 -927
  152. data/vendor/json_pure/install.rb +0 -23
  153. data/vendor/json_pure/java/src/json/ext/ByteListTranscoder.java +0 -167
  154. data/vendor/json_pure/java/src/json/ext/Generator.java +0 -444
  155. data/vendor/json_pure/java/src/json/ext/GeneratorMethods.java +0 -232
  156. data/vendor/json_pure/java/src/json/ext/GeneratorService.java +0 -43
  157. data/vendor/json_pure/java/src/json/ext/GeneratorState.java +0 -543
  158. data/vendor/json_pure/java/src/json/ext/OptionsReader.java +0 -114
  159. data/vendor/json_pure/java/src/json/ext/Parser.java +0 -2644
  160. data/vendor/json_pure/java/src/json/ext/Parser.rl +0 -968
  161. data/vendor/json_pure/java/src/json/ext/ParserService.java +0 -35
  162. data/vendor/json_pure/java/src/json/ext/RuntimeInfo.java +0 -121
  163. data/vendor/json_pure/java/src/json/ext/StringDecoder.java +0 -167
  164. data/vendor/json_pure/java/src/json/ext/StringEncoder.java +0 -106
  165. data/vendor/json_pure/java/src/json/ext/Utils.java +0 -89
  166. data/vendor/json_pure/json-java.gemspec +0 -23
  167. data/vendor/json_pure/json.gemspec +0 -37
  168. data/vendor/json_pure/json_pure.gemspec +0 -39
  169. data/vendor/json_pure/lib/json.rb +0 -62
  170. data/vendor/json_pure/lib/json/add/bigdecimal.rb +0 -28
  171. data/vendor/json_pure/lib/json/add/complex.rb +0 -22
  172. data/vendor/json_pure/lib/json/add/core.rb +0 -11
  173. data/vendor/json_pure/lib/json/add/date.rb +0 -34
  174. data/vendor/json_pure/lib/json/add/date_time.rb +0 -50
  175. data/vendor/json_pure/lib/json/add/exception.rb +0 -31
  176. data/vendor/json_pure/lib/json/add/ostruct.rb +0 -31
  177. data/vendor/json_pure/lib/json/add/range.rb +0 -29
  178. data/vendor/json_pure/lib/json/add/rational.rb +0 -22
  179. data/vendor/json_pure/lib/json/add/regexp.rb +0 -30
  180. data/vendor/json_pure/lib/json/add/struct.rb +0 -30
  181. data/vendor/json_pure/lib/json/add/symbol.rb +0 -25
  182. data/vendor/json_pure/lib/json/add/time.rb +0 -38
  183. data/vendor/json_pure/lib/json/common.rb +0 -487
  184. data/vendor/json_pure/lib/json/ext.rb +0 -21
  185. data/vendor/json_pure/lib/json/ext/.keep +0 -0
  186. data/vendor/json_pure/lib/json/generic_object.rb +0 -70
  187. data/vendor/json_pure/lib/json/pure.rb +0 -21
  188. data/vendor/json_pure/lib/json/pure/generator.rb +0 -522
  189. data/vendor/json_pure/lib/json/pure/parser.rb +0 -359
  190. data/vendor/json_pure/lib/json/version.rb +0 -8
  191. data/vendor/json_pure/tests/fixtures/fail1.json +0 -1
  192. data/vendor/json_pure/tests/fixtures/fail10.json +0 -1
  193. data/vendor/json_pure/tests/fixtures/fail11.json +0 -1
  194. data/vendor/json_pure/tests/fixtures/fail12.json +0 -1
  195. data/vendor/json_pure/tests/fixtures/fail13.json +0 -1
  196. data/vendor/json_pure/tests/fixtures/fail14.json +0 -1
  197. data/vendor/json_pure/tests/fixtures/fail18.json +0 -1
  198. data/vendor/json_pure/tests/fixtures/fail19.json +0 -1
  199. data/vendor/json_pure/tests/fixtures/fail2.json +0 -1
  200. data/vendor/json_pure/tests/fixtures/fail20.json +0 -1
  201. data/vendor/json_pure/tests/fixtures/fail21.json +0 -1
  202. data/vendor/json_pure/tests/fixtures/fail22.json +0 -1
  203. data/vendor/json_pure/tests/fixtures/fail23.json +0 -1
  204. data/vendor/json_pure/tests/fixtures/fail24.json +0 -1
  205. data/vendor/json_pure/tests/fixtures/fail25.json +0 -1
  206. data/vendor/json_pure/tests/fixtures/fail27.json +0 -2
  207. data/vendor/json_pure/tests/fixtures/fail28.json +0 -2
  208. data/vendor/json_pure/tests/fixtures/fail3.json +0 -1
  209. data/vendor/json_pure/tests/fixtures/fail4.json +0 -1
  210. data/vendor/json_pure/tests/fixtures/fail5.json +0 -1
  211. data/vendor/json_pure/tests/fixtures/fail6.json +0 -1
  212. data/vendor/json_pure/tests/fixtures/fail7.json +0 -1
  213. data/vendor/json_pure/tests/fixtures/fail8.json +0 -1
  214. data/vendor/json_pure/tests/fixtures/fail9.json +0 -1
  215. data/vendor/json_pure/tests/fixtures/pass1.json +0 -56
  216. data/vendor/json_pure/tests/fixtures/pass15.json +0 -1
  217. data/vendor/json_pure/tests/fixtures/pass16.json +0 -1
  218. data/vendor/json_pure/tests/fixtures/pass17.json +0 -1
  219. data/vendor/json_pure/tests/fixtures/pass2.json +0 -1
  220. data/vendor/json_pure/tests/fixtures/pass26.json +0 -1
  221. data/vendor/json_pure/tests/fixtures/pass3.json +0 -6
  222. data/vendor/json_pure/tests/setup_variant.rb +0 -11
  223. data/vendor/json_pure/tests/test_json.rb +0 -545
  224. data/vendor/json_pure/tests/test_json_addition.rb +0 -196
  225. data/vendor/json_pure/tests/test_json_encoding.rb +0 -65
  226. data/vendor/json_pure/tests/test_json_fixtures.rb +0 -35
  227. data/vendor/json_pure/tests/test_json_generate.rb +0 -322
  228. data/vendor/json_pure/tests/test_json_generic_object.rb +0 -75
  229. data/vendor/json_pure/tests/test_json_string_matching.rb +0 -39
  230. data/vendor/json_pure/tests/test_json_unicode.rb +0 -72
  231. data/vendor/json_pure/tools/fuzz.rb +0 -139
  232. data/vendor/json_pure/tools/server.rb +0 -62
  233. data/vendor/pusher-gem/Gemfile +0 -2
  234. data/vendor/pusher-gem/README.md +0 -80
  235. data/vendor/pusher-gem/lib/pusher.rb +0 -107
  236. data/vendor/pusher-gem/lib/pusher/request.rb +0 -107
  237. data/vendor/pusher-gem/spec/channel_spec.rb +0 -274
  238. data/vendor/pusher-gem/spec/pusher_spec.rb +0 -87
  239. data/vendor/ruby-hmac/History.txt +0 -15
  240. data/vendor/ruby-hmac/Manifest.txt +0 -11
  241. data/vendor/ruby-hmac/README.md +0 -41
  242. data/vendor/ruby-hmac/Rakefile +0 -23
  243. data/vendor/ruby-hmac/lib/hmac-md5.rb +0 -11
  244. data/vendor/ruby-hmac/lib/hmac-rmd160.rb +0 -11
  245. data/vendor/ruby-hmac/lib/hmac-sha1.rb +0 -11
  246. data/vendor/ruby-hmac/lib/hmac-sha2.rb +0 -25
  247. data/vendor/ruby-hmac/lib/hmac.rb +0 -118
  248. data/vendor/ruby-hmac/lib/ruby_hmac.rb +0 -2
  249. data/vendor/ruby-hmac/ruby-hmac.gemspec +0 -33
  250. data/vendor/ruby-hmac/test/test_hmac.rb +0 -89
  251. data/vendor/signature/VERSION +0 -1
@@ -11,3 +11,15 @@ require 'webmock/rspec'
11
11
 
12
12
  require 'pusher'
13
13
  require 'eventmachine'
14
+
15
+ RSpec.configure do |config|
16
+ config.before(:each) do
17
+ WebMock.reset!
18
+ WebMock.disable_net_connect!
19
+ end
20
+ end
21
+
22
+ def hmac(key, data)
23
+ digest = OpenSSL::Digest::SHA256.new
24
+ OpenSSL::HMAC.hexdigest(digest, key, data)
25
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rack'
4
+ require 'stringio'
5
+
6
+ describe Pusher::WebHook do
7
+ before :each do
8
+ @hook_data = {
9
+ "time_ms" => 123456,
10
+ "events" => [
11
+ {"name" => 'foo'}
12
+ ]
13
+ }
14
+ end
15
+
16
+ describe "initialization" do
17
+ it "can be initialized with Rack::Request" do
18
+ request = Rack::Request.new({
19
+ 'HTTP_X_PUSHER_KEY' => '1234',
20
+ 'HTTP_X_PUSHER_SIGNATURE' => 'asdf',
21
+ 'CONTENT_TYPE' => 'application/json',
22
+ 'rack.input' => StringIO.new(MultiJson.encode(@hook_data))
23
+ })
24
+ wh = Pusher::WebHook.new(request)
25
+ wh.key.should == '1234'
26
+ wh.signature.should == 'asdf'
27
+ wh.data.should == @hook_data
28
+ end
29
+
30
+ it "can be initialized with a hash" do
31
+ request = {
32
+ :key => '1234',
33
+ :signature => 'asdf',
34
+ :content_type => 'application/json',
35
+ :body => MultiJson.encode(@hook_data),
36
+ }
37
+ wh = Pusher::WebHook.new(request)
38
+ wh.key.should == '1234'
39
+ wh.signature.should == 'asdf'
40
+ wh.data.should == @hook_data
41
+ end
42
+ end
43
+
44
+ describe "after initialization" do
45
+ before :each do
46
+ @body = MultiJson.encode(@hook_data)
47
+ request = {
48
+ :key => '1234',
49
+ :signature => hmac('asdf', @body),
50
+ :content_type => 'application/json',
51
+ :body => @body
52
+ }
53
+
54
+ @client = Pusher::Client.new
55
+ @wh = Pusher::WebHook.new(request, @client)
56
+ end
57
+
58
+ it "should validate" do
59
+ @client.key = '1234'
60
+ @client.secret = 'asdf'
61
+ @wh.should be_valid
62
+ end
63
+
64
+ it "should not validate if key is wrong" do
65
+ @client.key = '12345'
66
+ @client.secret = 'asdf'
67
+ Pusher.logger.should_receive(:warn).with("Received webhook with unknown key: 1234")
68
+ @wh.should_not be_valid
69
+ end
70
+
71
+ it "should not validate if secret is wrong" do
72
+ @client.key = '1234'
73
+ @client.secret = 'asdfxxx'
74
+ digest = OpenSSL::Digest::SHA256.new
75
+ expected = OpenSSL::HMAC.hexdigest(digest, @client.secret, @body)
76
+ Pusher.logger.should_receive(:warn).with("Received WebHook with invalid signature: got #{@wh.signature}, expected #{expected}")
77
+ @wh.should_not be_valid
78
+ end
79
+
80
+ it "should validate with an extra token" do
81
+ @client.key = '12345'
82
+ @client.secret = 'xxx'
83
+ @wh.valid?({:key => '1234', :secret => 'asdf'}).should be_true
84
+ end
85
+
86
+ it "should validate with an array of extra tokens" do
87
+ @client.key = '123456'
88
+ @client.secret = 'xxx'
89
+ @wh.valid?([
90
+ {:key => '12345', :secret => 'wtf'},
91
+ {:key => '1234', :secret => 'asdf'}
92
+ ]).should be_true
93
+ end
94
+
95
+ it "should not validate if all keys are wrong with extra tokens" do
96
+ @client.key = '123456'
97
+ @client.secret = 'asdf'
98
+ Pusher.logger.should_receive(:warn).with("Received webhook with unknown key: 1234")
99
+ @wh.valid?({:key => '12345', :secret => 'asdf'}).should be_false
100
+ end
101
+
102
+ it "should not validate if secret is wrong with extra tokens" do
103
+ @client.key = '123456'
104
+ @client.secret = 'asdfxxx'
105
+ Pusher.logger.should_receive(:warn).with(/Received WebHook with invalid signature/)
106
+ @wh.valid?({:key => '1234', :secret => 'wtf'}).should be_false
107
+ end
108
+
109
+ it "should expose events" do
110
+ @wh.events.should == @hook_data["events"]
111
+ end
112
+
113
+ it "should expose time" do
114
+ @wh.time.should == Time.at(123.456)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode # JRuby in 1.8 mode
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-18mode
9
+ - rbx-19mode
10
+ matrix:
11
+ allow_failures:
12
+ - rvm: rbx-18mode
13
+ - rvm: rbx-19mode
14
+
15
+ script: bundle exec rspec spec
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
@@ -1,47 +1,55 @@
1
1
  signature
2
2
  =========
3
3
 
4
+ [![Build Status](https://secure.travis-ci.org/mloughran/signature.png?branch=master)](http://travis-ci.org/mloughran/signature)
5
+
4
6
  Examples
5
7
  --------
6
8
 
7
9
  Client example
8
10
 
9
- params = {:some => 'parameters'}
10
- token = Signature::Token.new('my_key', 'my_secret')
11
- request = Signature::Request.new('POST', '/api/thing', params)
12
- auth_hash = request.sign(token)
13
- query_params = params.merge(auth_hash)
14
-
15
- HTTParty.post('http://myservice/api/thing', {
16
- :query => query_params
17
- })
11
+ ```ruby
12
+ params = {:some => 'parameters'}
13
+ token = Signature::Token.new('my_key', 'my_secret')
14
+ request = Signature::Request.new('POST', '/api/thing', params)
15
+ auth_hash = request.sign(token)
16
+ query_params = params.merge(auth_hash)
17
+
18
+ HTTParty.post('http://myservice/api/thing', {
19
+ :query => query_params
20
+ })
21
+ ```
18
22
 
19
23
  `query_params` looks like:
20
24
 
21
- {
22
- :some => "parameters",
23
- :auth_timestamp => 1273231888,
24
- :auth_signature => "28b6bb0f242f71064916fad6ae463fe91f5adc302222dfc02c348ae1941eaf80",
25
- :auth_version => "1.0",
26
- :auth_key => "my_key"
27
- }
25
+ ```ruby
26
+ {
27
+ :some => "parameters",
28
+ :auth_timestamp => 1273231888,
29
+ :auth_signature => "28b6bb0f242f71064916fad6ae463fe91f5adc302222dfc02c348ae1941eaf80",
30
+ :auth_version => "1.0",
31
+ :auth_key => "my_key"
32
+ }
28
33
 
34
+ ```
29
35
  Server example (sinatra)
30
36
 
31
- error Signature::AuthenticationError do |controller|
32
- error = controller.env["sinatra.error"]
33
- halt 401, "401 UNAUTHORIZED: #{error.message}\n"
34
- end
37
+ ```ruby
38
+ error Signature::AuthenticationError do |controller|
39
+ error = controller.env["sinatra.error"]
40
+ halt 401, "401 UNAUTHORIZED: #{error.message}\n"
41
+ end
35
42
 
36
- post '/api/thing' do
37
- request = Signature::Request.new('POST', env["REQUEST_PATH"], params)
38
- # This will raise a Signature::AuthenticationError if request does not authenticate
39
- token = request.authenticate do |key|
40
- Signature::Token.new(key, lookup_secret(key))
41
- end
43
+ post '/api/thing' do
44
+ request = Signature::Request.new('POST', env["REQUEST_PATH"], params)
45
+ # This will raise a Signature::AuthenticationError if request does not authenticate
46
+ token = request.authenticate do |key|
47
+ Signature::Token.new(key, lookup_secret(key))
48
+ end
42
49
 
43
- # Do whatever you need to do
44
- end
50
+ # Do whatever you need to do
51
+ end
52
+ ```
45
53
 
46
54
  Developing
47
55
  ----------
@@ -49,6 +57,8 @@ Developing
49
57
  bundle
50
58
  bundle exec rspec spec/*_spec.rb
51
59
 
60
+ Please see the travis status for a list of rubies tested against
61
+
52
62
  Copyright
53
63
  ---------
54
64
 
@@ -1,4 +1,6 @@
1
- require 'hmac-sha2'
1
+ require 'openssl'
2
+
3
+ require 'signature/query_encoder'
2
4
 
3
5
  module Signature
4
6
  class AuthenticationError < RuntimeError; end
@@ -18,6 +20,8 @@ module Signature
18
20
  class Request
19
21
  attr_accessor :path, :query_hash
20
22
 
23
+ include QueryEncoder
24
+
21
25
  # http://www.w3.org/TR/NOTE-datetime
22
26
  ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
23
27
 
@@ -34,63 +38,139 @@ module Signature
34
38
 
35
39
  @method = method.upcase
36
40
  @path, @query_hash, @auth_hash = path, query_hash, auth_hash
41
+ @signed = false
37
42
  end
38
43
 
44
+ # Sign the request with the given token, and return the computed
45
+ # authentication parameters
46
+ #
39
47
  def sign(token)
40
48
  @auth_hash = {
41
49
  :auth_version => "1.0",
42
50
  :auth_key => token.key,
43
51
  :auth_timestamp => Time.now.to_i.to_s
44
52
  }
45
-
46
53
  @auth_hash[:auth_signature] = signature(token)
47
54
 
55
+ @signed = true
56
+
48
57
  return @auth_hash
49
58
  end
50
59
 
51
60
  # Authenticates the request with a token
52
61
  #
53
- # Timestamp check: Unless timestamp_grace is set to nil (which will skip
54
- # the timestamp check), an exception will be raised if timestamp is not
55
- # supplied or if the timestamp provided is not within timestamp_grace of
56
- # the real time (defaults to 10 minutes)
62
+ # Raises an AuthenticationError if the request is invalid.
63
+ # AuthenticationError exception messages are designed to be exposed to API
64
+ # consumers, and should help them correct errors generating signatures
65
+ #
66
+ # Timestamp: Unless timestamp_grace is set to nil (which allows this check
67
+ # to be skipped), AuthenticationError will be raised if the timestamp is
68
+ # missing or further than timestamp_grace period away from the real time
69
+ # (defaults to 10 minutes)
57
70
  #
58
- # Signature check: Raises an exception if the signature does not match the
59
- # computed value
71
+ # Signature: Raises AuthenticationError if the signature does not match
72
+ # the computed HMAC. The error contains a hint for how to sign.
60
73
  #
61
74
  def authenticate_by_token!(token, timestamp_grace = 600)
75
+ # Validate that your code has provided a valid token. This does not
76
+ # raise an AuthenticationError since passing tokens with empty secret is
77
+ # a code error which should be fixed, not reported to the API's consumer
78
+ if token.secret.nil? || token.secret.empty?
79
+ raise "Provided token is missing secret"
80
+ end
81
+
62
82
  validate_version!
63
83
  validate_timestamp!(timestamp_grace)
64
84
  validate_signature!(token)
65
85
  true
66
86
  end
67
87
 
88
+ # Authenticate the request with a token, but rather than raising an
89
+ # exception if the request is invalid, simply returns false
90
+ #
68
91
  def authenticate_by_token(token, timestamp_grace = 600)
69
92
  authenticate_by_token!(token, timestamp_grace)
70
93
  rescue AuthenticationError
71
94
  false
72
95
  end
73
96
 
74
- def authenticate(timestamp_grace = 600, &block)
97
+ # Authenticate a request
98
+ #
99
+ # Takes a block which will be called with the auth_key from the request,
100
+ # and which should return a Signature::Token (or nil if no token can be
101
+ # found for the key)
102
+ #
103
+ # Raises errors in the same way as authenticate_by_token!
104
+ #
105
+ def authenticate(timestamp_grace = 600)
106
+ raise ArgumentError, "Block required" unless block_given?
75
107
  key = @auth_hash['auth_key']
76
- raise AuthenticationError, "Authentication key required" unless key
108
+ raise AuthenticationError, "Missing parameter: auth_key" unless key
77
109
  token = yield key
78
- unless token && token.secret
79
- raise AuthenticationError, "Invalid authentication key"
110
+ unless token
111
+ raise AuthenticationError, "Unknown auth_key"
80
112
  end
81
113
  authenticate_by_token!(token, timestamp_grace)
82
114
  return token
83
115
  end
84
116
 
117
+ # Authenticate a request asynchronously
118
+ #
119
+ # This method is useful it you're running a server inside eventmachine and
120
+ # need to lookup the token asynchronously.
121
+ #
122
+ # The block is passed an auth key and a deferrable which should succeed
123
+ # with the token, or fail if the token cannot be found
124
+ #
125
+ # This method returns a deferrable which succeeds with the valid token, or
126
+ # fails with an AuthenticationError which can be used to pass the error
127
+ # back to the user
128
+ #
129
+ def authenticate_async(timestamp_grace = 600)
130
+ raise ArgumentError, "Block required" unless block_given?
131
+ df = EM::DefaultDeferrable.new
132
+
133
+ key = @auth_hash['auth_key']
134
+
135
+ unless key
136
+ df.fail(AuthenticationError.new("Missing parameter: auth_key"))
137
+ return
138
+ end
139
+
140
+ token_df = yield key
141
+ token_df.callback { |token|
142
+ begin
143
+ authenticate_by_token!(token, timestamp_grace)
144
+ df.succeed(token)
145
+ rescue AuthenticationError => e
146
+ df.fail(e)
147
+ end
148
+ }
149
+ token_df.errback {
150
+ df.fail(AuthenticationError.new("Unknown auth_key"))
151
+ }
152
+ ensure
153
+ return df
154
+ end
155
+
156
+ # Expose the authentication parameters for a signed request
157
+ #
85
158
  def auth_hash
86
- raise "Request not signed" unless @auth_hash && @auth_hash[:auth_signature]
159
+ raise "Request not signed" unless @signed
87
160
  @auth_hash
88
161
  end
89
162
 
163
+ # Query parameters merged with the computed authentication parameters
164
+ #
165
+ def signed_params
166
+ @query_hash.merge(auth_hash)
167
+ end
168
+
90
169
  private
91
170
 
92
171
  def signature(token)
93
- HMAC::SHA256.hexdigest(token.secret, string_to_sign)
172
+ digest = OpenSSL::Digest::SHA256.new
173
+ OpenSSL::HMAC.hexdigest(digest, token.secret, string_to_sign)
94
174
  end
95
175
 
96
176
  def string_to_sign
@@ -106,7 +186,9 @@ module Signature
106
186
  # Exclude signature from signature generation!
107
187
  hash.delete("auth_signature")
108
188
 
109
- hash.keys.sort.map { |k| "#{k}=#{hash[k]}" }.join("&")
189
+ hash.sort.map do |k, v|
190
+ QueryEncoder.encode_param_without_escaping(k, v)
191
+ end.join('&')
110
192
  end
111
193
 
112
194
  def validate_version!
@@ -0,0 +1,47 @@
1
+ module Signature
2
+ # Query string encoding extracted with thanks from em-http-request
3
+ module QueryEncoder
4
+ class << self
5
+ # URL encodes query parameters:
6
+ # single k=v, or a URL encoded array, if v is an array of values
7
+ def encode_param(k, v)
8
+ if v.is_a?(Array)
9
+ v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
10
+ else
11
+ escape(k) + "=" + escape(v)
12
+ end
13
+ end
14
+
15
+ # Like encode_param, but doesn't url escape keys or values
16
+ def encode_param_without_escaping(k, v)
17
+ if v.is_a?(Array)
18
+ v.map { |e| k + "[]=" + e }.join("&")
19
+ else
20
+ "#{k}=#{v}"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def escape(s)
27
+ if defined?(EscapeUtils)
28
+ EscapeUtils.escape_url(s.to_s)
29
+ else
30
+ s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {
31
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
32
+ }
33
+ end
34
+ end
35
+
36
+ if ''.respond_to?(:bytesize)
37
+ def bytesize(string)
38
+ string.bytesize
39
+ end
40
+ else
41
+ def bytesize(string)
42
+ string.size
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end