scout 5.7.1 → 5.7.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +5 -0
- data/lib/scout/command/run.rb +1 -1
- data/lib/scout/server.rb +2 -1
- data/lib/scout/streamer.rb +3 -2
- data/lib/scout/streamer_daemon.rb +4 -4
- data/lib/scout/version.rb +1 -1
- data/test/scout_test.rb +7 -8
- data/vendor/httpclient/README.txt +759 -0
- data/vendor/httpclient/bin/httpclient +65 -0
- data/vendor/httpclient/lib/hexdump.rb +50 -0
- data/vendor/httpclient/lib/http-access2.rb +55 -0
- data/vendor/httpclient/lib/http-access2/cookie.rb +1 -0
- data/vendor/httpclient/lib/http-access2/http.rb +1 -0
- data/vendor/httpclient/lib/httpclient.rb +1156 -0
- data/vendor/httpclient/lib/httpclient/auth.rb +899 -0
- data/vendor/httpclient/lib/httpclient/cacert.p7s +1912 -0
- data/vendor/httpclient/lib/httpclient/connection.rb +88 -0
- data/vendor/httpclient/lib/httpclient/cookie.rb +438 -0
- data/vendor/httpclient/lib/httpclient/http.rb +1046 -0
- data/vendor/httpclient/lib/httpclient/include_client.rb +83 -0
- data/vendor/httpclient/lib/httpclient/session.rb +1025 -0
- data/vendor/httpclient/lib/httpclient/ssl_config.rb +403 -0
- data/vendor/httpclient/lib/httpclient/timeout.rb +140 -0
- data/vendor/httpclient/lib/httpclient/util.rb +178 -0
- data/vendor/httpclient/lib/httpclient/version.rb +3 -0
- data/vendor/httpclient/lib/oauthclient.rb +110 -0
- data/vendor/httpclient/sample/async.rb +8 -0
- data/vendor/httpclient/sample/auth.rb +11 -0
- data/vendor/httpclient/sample/cookie.rb +18 -0
- data/vendor/httpclient/sample/dav.rb +103 -0
- data/vendor/httpclient/sample/howto.rb +49 -0
- data/vendor/httpclient/sample/oauth_buzz.rb +57 -0
- data/vendor/httpclient/sample/oauth_friendfeed.rb +59 -0
- data/vendor/httpclient/sample/oauth_salesforce_10.rb +63 -0
- data/vendor/httpclient/sample/oauth_twitter.rb +61 -0
- data/vendor/httpclient/sample/ssl/0cert.pem +22 -0
- data/vendor/httpclient/sample/ssl/0key.pem +30 -0
- data/vendor/httpclient/sample/ssl/1000cert.pem +19 -0
- data/vendor/httpclient/sample/ssl/1000key.pem +18 -0
- data/vendor/httpclient/sample/ssl/htdocs/index.html +10 -0
- data/vendor/httpclient/sample/ssl/ssl_client.rb +22 -0
- data/vendor/httpclient/sample/ssl/webrick_httpsd.rb +29 -0
- data/vendor/httpclient/sample/stream.rb +21 -0
- data/vendor/httpclient/sample/thread.rb +27 -0
- data/vendor/httpclient/sample/wcat.rb +21 -0
- data/vendor/httpclient/test/ca-chain.cert +44 -0
- data/vendor/httpclient/test/ca.cert +23 -0
- data/vendor/httpclient/test/client.cert +19 -0
- data/vendor/httpclient/test/client.key +15 -0
- data/vendor/httpclient/test/helper.rb +129 -0
- data/vendor/httpclient/test/htdigest +1 -0
- data/vendor/httpclient/test/htpasswd +2 -0
- data/vendor/httpclient/test/runner.rb +2 -0
- data/vendor/httpclient/test/server.cert +19 -0
- data/vendor/httpclient/test/server.key +15 -0
- data/vendor/httpclient/test/sslsvr.rb +65 -0
- data/vendor/httpclient/test/subca.cert +21 -0
- data/vendor/httpclient/test/test_auth.rb +321 -0
- data/vendor/httpclient/test/test_cookie.rb +391 -0
- data/vendor/httpclient/test/test_hexdump.rb +14 -0
- data/vendor/httpclient/test/test_http-access2.rb +507 -0
- data/vendor/httpclient/test/test_httpclient.rb +1783 -0
- data/vendor/httpclient/test/test_include_client.rb +52 -0
- data/vendor/httpclient/test/test_ssl.rb +235 -0
- data/vendor/multi_json/.document +5 -0
- data/vendor/multi_json/.rspec +3 -0
- data/vendor/multi_json/.travis.yml +11 -0
- data/vendor/multi_json/.yardopts +6 -0
- data/vendor/multi_json/CHANGELOG.md +169 -0
- data/vendor/multi_json/CONTRIBUTING.md +46 -0
- data/vendor/multi_json/Gemfile +31 -0
- data/vendor/multi_json/LICENSE.md +20 -0
- data/vendor/multi_json/README.md +109 -0
- data/vendor/multi_json/Rakefile +12 -0
- data/vendor/multi_json/lib/multi_json.rb +157 -0
- data/vendor/multi_json/lib/multi_json/adapter.rb +48 -0
- data/vendor/multi_json/lib/multi_json/adapters/gson.rb +19 -0
- data/vendor/multi_json/lib/multi_json/adapters/jr_jackson.rb +19 -0
- data/vendor/multi_json/lib/multi_json/adapters/json_common.rb +25 -0
- data/vendor/multi_json/lib/multi_json/adapters/json_gem.rb +11 -0
- data/vendor/multi_json/lib/multi_json/adapters/json_pure.rb +11 -0
- data/vendor/multi_json/lib/multi_json/adapters/nsjsonserialization.rb +34 -0
- data/vendor/multi_json/lib/multi_json/adapters/oj.rb +24 -0
- data/vendor/multi_json/lib/multi_json/adapters/ok_json.rb +22 -0
- data/vendor/multi_json/lib/multi_json/adapters/yajl.rb +19 -0
- data/vendor/multi_json/lib/multi_json/convertible_hash_keys.rb +43 -0
- data/vendor/multi_json/lib/multi_json/load_error.rb +11 -0
- data/vendor/multi_json/lib/multi_json/options.rb +48 -0
- data/vendor/multi_json/lib/multi_json/vendor/okjson.rb +606 -0
- data/vendor/multi_json/lib/multi_json/version.rb +20 -0
- data/vendor/multi_json/multi_json.gemspec +22 -0
- data/vendor/multi_json/spec/adapter_shared_example.rb +235 -0
- data/vendor/multi_json/spec/has_options.rb +119 -0
- data/vendor/multi_json/spec/helper.rb +35 -0
- data/vendor/multi_json/spec/json_common_shared_example.rb +30 -0
- data/vendor/multi_json/spec/multi_json_spec.rb +226 -0
- data/vendor/{signature → pusher}/.document +0 -0
- data/vendor/{json_pure/diagrams/.keep → pusher/.gemtest} +0 -0
- data/vendor/pusher/.gitignore +23 -0
- data/vendor/pusher/.travis.yml +15 -0
- data/vendor/pusher/Gemfile +2 -0
- data/vendor/{pusher-gem → pusher}/LICENSE +1 -1
- data/vendor/pusher/README.md +186 -0
- data/vendor/{pusher-gem → pusher}/Rakefile +0 -0
- data/vendor/{pusher-gem → pusher}/examples/async_message.rb +0 -0
- data/vendor/pusher/lib/pusher.rb +60 -0
- data/vendor/{pusher-gem → pusher}/lib/pusher/channel.rb +47 -54
- data/vendor/pusher/lib/pusher/client.rb +306 -0
- data/vendor/pusher/lib/pusher/request.rb +107 -0
- data/vendor/pusher/lib/pusher/resource.rb +36 -0
- data/vendor/pusher/lib/pusher/webhook.rb +110 -0
- data/vendor/{pusher-gem → pusher}/pusher.gemspec +6 -5
- data/vendor/pusher/spec/channel_spec.rb +127 -0
- data/vendor/pusher/spec/client_spec.rb +464 -0
- data/vendor/{pusher-gem → pusher}/spec/spec_helper.rb +12 -0
- data/vendor/pusher/spec/web_hook_spec.rb +117 -0
- data/vendor/signature/.travis.yml +15 -0
- data/vendor/signature/Gemfile +1 -1
- data/vendor/signature/README.md +38 -28
- data/vendor/signature/lib/signature.rb +97 -15
- data/vendor/signature/lib/signature/query_encoder.rb +47 -0
- data/vendor/signature/lib/signature/version.rb +1 -1
- data/vendor/signature/signature.gemspec +3 -2
- data/vendor/signature/spec/signature_spec.rb +164 -55
- data/vendor/signature/spec/spec_helper.rb +2 -3
- metadata +120 -145
- data/vendor/json_pure/.gitignore +0 -12
- data/vendor/json_pure/.travis.yml +0 -20
- data/vendor/json_pure/CHANGES +0 -282
- data/vendor/json_pure/COPYING +0 -58
- data/vendor/json_pure/COPYING-json-jruby +0 -57
- data/vendor/json_pure/GPL +0 -340
- data/vendor/json_pure/Gemfile +0 -11
- data/vendor/json_pure/README-json-jruby.markdown +0 -33
- data/vendor/json_pure/README.rdoc +0 -358
- data/vendor/json_pure/Rakefile +0 -412
- data/vendor/json_pure/TODO +0 -1
- data/vendor/json_pure/VERSION +0 -1
- data/vendor/json_pure/data/example.json +0 -1
- data/vendor/json_pure/data/index.html +0 -38
- data/vendor/json_pure/data/prototype.js +0 -4184
- data/vendor/json_pure/ext/json/ext/fbuffer/fbuffer.h +0 -181
- data/vendor/json_pure/ext/json/ext/generator/depend +0 -1
- data/vendor/json_pure/ext/json/ext/generator/extconf.rb +0 -14
- data/vendor/json_pure/ext/json/ext/generator/generator.c +0 -1435
- data/vendor/json_pure/ext/json/ext/generator/generator.h +0 -148
- data/vendor/json_pure/ext/json/ext/parser/depend +0 -1
- data/vendor/json_pure/ext/json/ext/parser/extconf.rb +0 -13
- data/vendor/json_pure/ext/json/ext/parser/parser.c +0 -2204
- data/vendor/json_pure/ext/json/ext/parser/parser.h +0 -77
- data/vendor/json_pure/ext/json/ext/parser/parser.rl +0 -927
- data/vendor/json_pure/install.rb +0 -23
- data/vendor/json_pure/java/src/json/ext/ByteListTranscoder.java +0 -167
- data/vendor/json_pure/java/src/json/ext/Generator.java +0 -444
- data/vendor/json_pure/java/src/json/ext/GeneratorMethods.java +0 -232
- data/vendor/json_pure/java/src/json/ext/GeneratorService.java +0 -43
- data/vendor/json_pure/java/src/json/ext/GeneratorState.java +0 -543
- data/vendor/json_pure/java/src/json/ext/OptionsReader.java +0 -114
- data/vendor/json_pure/java/src/json/ext/Parser.java +0 -2644
- data/vendor/json_pure/java/src/json/ext/Parser.rl +0 -968
- data/vendor/json_pure/java/src/json/ext/ParserService.java +0 -35
- data/vendor/json_pure/java/src/json/ext/RuntimeInfo.java +0 -121
- data/vendor/json_pure/java/src/json/ext/StringDecoder.java +0 -167
- data/vendor/json_pure/java/src/json/ext/StringEncoder.java +0 -106
- data/vendor/json_pure/java/src/json/ext/Utils.java +0 -89
- data/vendor/json_pure/json-java.gemspec +0 -23
- data/vendor/json_pure/json.gemspec +0 -37
- data/vendor/json_pure/json_pure.gemspec +0 -39
- data/vendor/json_pure/lib/json.rb +0 -62
- data/vendor/json_pure/lib/json/add/bigdecimal.rb +0 -28
- data/vendor/json_pure/lib/json/add/complex.rb +0 -22
- data/vendor/json_pure/lib/json/add/core.rb +0 -11
- data/vendor/json_pure/lib/json/add/date.rb +0 -34
- data/vendor/json_pure/lib/json/add/date_time.rb +0 -50
- data/vendor/json_pure/lib/json/add/exception.rb +0 -31
- data/vendor/json_pure/lib/json/add/ostruct.rb +0 -31
- data/vendor/json_pure/lib/json/add/range.rb +0 -29
- data/vendor/json_pure/lib/json/add/rational.rb +0 -22
- data/vendor/json_pure/lib/json/add/regexp.rb +0 -30
- data/vendor/json_pure/lib/json/add/struct.rb +0 -30
- data/vendor/json_pure/lib/json/add/symbol.rb +0 -25
- data/vendor/json_pure/lib/json/add/time.rb +0 -38
- data/vendor/json_pure/lib/json/common.rb +0 -487
- data/vendor/json_pure/lib/json/ext.rb +0 -21
- data/vendor/json_pure/lib/json/ext/.keep +0 -0
- data/vendor/json_pure/lib/json/generic_object.rb +0 -70
- data/vendor/json_pure/lib/json/pure.rb +0 -21
- data/vendor/json_pure/lib/json/pure/generator.rb +0 -522
- data/vendor/json_pure/lib/json/pure/parser.rb +0 -359
- data/vendor/json_pure/lib/json/version.rb +0 -8
- data/vendor/json_pure/tests/fixtures/fail1.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail10.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail11.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail12.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail13.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail14.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail18.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail19.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail2.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail20.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail21.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail22.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail23.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail24.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail25.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail27.json +0 -2
- data/vendor/json_pure/tests/fixtures/fail28.json +0 -2
- data/vendor/json_pure/tests/fixtures/fail3.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail4.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail5.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail6.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail7.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail8.json +0 -1
- data/vendor/json_pure/tests/fixtures/fail9.json +0 -1
- data/vendor/json_pure/tests/fixtures/pass1.json +0 -56
- data/vendor/json_pure/tests/fixtures/pass15.json +0 -1
- data/vendor/json_pure/tests/fixtures/pass16.json +0 -1
- data/vendor/json_pure/tests/fixtures/pass17.json +0 -1
- data/vendor/json_pure/tests/fixtures/pass2.json +0 -1
- data/vendor/json_pure/tests/fixtures/pass26.json +0 -1
- data/vendor/json_pure/tests/fixtures/pass3.json +0 -6
- data/vendor/json_pure/tests/setup_variant.rb +0 -11
- data/vendor/json_pure/tests/test_json.rb +0 -545
- data/vendor/json_pure/tests/test_json_addition.rb +0 -196
- data/vendor/json_pure/tests/test_json_encoding.rb +0 -65
- data/vendor/json_pure/tests/test_json_fixtures.rb +0 -35
- data/vendor/json_pure/tests/test_json_generate.rb +0 -322
- data/vendor/json_pure/tests/test_json_generic_object.rb +0 -75
- data/vendor/json_pure/tests/test_json_string_matching.rb +0 -39
- data/vendor/json_pure/tests/test_json_unicode.rb +0 -72
- data/vendor/json_pure/tools/fuzz.rb +0 -139
- data/vendor/json_pure/tools/server.rb +0 -62
- data/vendor/pusher-gem/Gemfile +0 -2
- data/vendor/pusher-gem/README.md +0 -80
- data/vendor/pusher-gem/lib/pusher.rb +0 -107
- data/vendor/pusher-gem/lib/pusher/request.rb +0 -107
- data/vendor/pusher-gem/spec/channel_spec.rb +0 -274
- data/vendor/pusher-gem/spec/pusher_spec.rb +0 -87
- data/vendor/ruby-hmac/History.txt +0 -15
- data/vendor/ruby-hmac/Manifest.txt +0 -11
- data/vendor/ruby-hmac/README.md +0 -41
- data/vendor/ruby-hmac/Rakefile +0 -23
- data/vendor/ruby-hmac/lib/hmac-md5.rb +0 -11
- data/vendor/ruby-hmac/lib/hmac-rmd160.rb +0 -11
- data/vendor/ruby-hmac/lib/hmac-sha1.rb +0 -11
- data/vendor/ruby-hmac/lib/hmac-sha2.rb +0 -25
- data/vendor/ruby-hmac/lib/hmac.rb +0 -118
- data/vendor/ruby-hmac/lib/ruby_hmac.rb +0 -2
- data/vendor/ruby-hmac/ruby-hmac.gemspec +0 -33
- data/vendor/ruby-hmac/test/test_hmac.rb +0 -89
- 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
|
data/vendor/signature/Gemfile
CHANGED
data/vendor/signature/README.md
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
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 '
|
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
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
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
|
59
|
-
# computed
|
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
|
-
|
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, "
|
108
|
+
raise AuthenticationError, "Missing parameter: auth_key" unless key
|
77
109
|
token = yield key
|
78
|
-
unless token
|
79
|
-
raise AuthenticationError, "
|
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 @
|
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
|
-
|
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.
|
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
|