pusher 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +1 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/pusher.rb +2 -2
- data/lib/pusher/channel.rb +23 -43
- data/lib/pusher/request.rb +38 -0
- data/pusher.gemspec +7 -6
- data/spec/pusher_spec.rb +21 -1
- metadata +21 -11
- data/lib/pusher/authentication.rb +0 -142
- data/spec/authentication_spec.rb +0 -176
data/README.md
CHANGED
@@ -30,7 +30,7 @@ Asynchronous triggering
|
|
30
30
|
|
31
31
|
To avoid blocking in a typical web application, if you are running inside eventmachine (for example if you use the thin server), you may wish to use the `trigger_async` method which uses the em-http-request gem to make api requests to pusher. It returns a deferrable which you can optionally bind to with success and failure callbacks. This is not a gem dependency, so you will need to install it manually.
|
32
32
|
|
33
|
-
d = Pusher['a_channel'].
|
33
|
+
d = Pusher['a_channel'].trigger_async('an_event', {:some => 'data'}, socket_id)
|
34
34
|
d.callback {
|
35
35
|
# Do something on success
|
36
36
|
}
|
data/Rakefile
CHANGED
@@ -14,6 +14,7 @@ begin
|
|
14
14
|
gem.add_dependency "json"
|
15
15
|
gem.add_dependency "crack"
|
16
16
|
gem.add_dependency "ruby-hmac"
|
17
|
+
gem.add_dependency 'signature'
|
17
18
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
18
19
|
gem.add_development_dependency "webmock"
|
19
20
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/pusher.rb
CHANGED
@@ -18,7 +18,7 @@ module Pusher
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def authentication_token
|
21
|
-
|
21
|
+
Signature::Token.new(@key, @secret)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -36,4 +36,4 @@ module Pusher
|
|
36
36
|
end
|
37
37
|
|
38
38
|
require 'pusher/channel'
|
39
|
-
require 'pusher/
|
39
|
+
require 'pusher/request'
|
data/lib/pusher/channel.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'crack/core_extensions' # Used for Hash#to_params
|
2
|
+
require 'signature'
|
2
3
|
require 'digest/md5'
|
4
|
+
require 'hmac-sha2'
|
3
5
|
|
4
6
|
require 'json'
|
5
7
|
require 'uri'
|
6
8
|
|
7
9
|
module Pusher
|
8
10
|
class Channel
|
11
|
+
attr_reader :name
|
12
|
+
|
9
13
|
def initialize(app_id, name)
|
14
|
+
@name = name
|
10
15
|
@uri = URI::HTTP.build({
|
11
16
|
:host => Pusher.host,
|
12
17
|
:port => Pusher.port,
|
@@ -21,13 +26,13 @@ module Pusher
|
|
21
26
|
require 'em-http' unless defined?(EventMachine::HttpRequest)
|
22
27
|
|
23
28
|
@http_async ||= EventMachine::HttpRequest.new(@uri)
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
|
30
|
+
request = Pusher::Request.new(@uri, event_name, data, socket_id)
|
31
|
+
|
27
32
|
deferrable = EM::DefaultDeferrable.new
|
28
33
|
|
29
34
|
http = @http_async.post({
|
30
|
-
:query => query, :timeout => 2, :body => body
|
35
|
+
:query => request.query, :timeout => 2, :body => request.body
|
31
36
|
})
|
32
37
|
http.callback {
|
33
38
|
begin
|
@@ -49,12 +54,11 @@ module Pusher
|
|
49
54
|
require 'net/http' unless defined?(Net::HTTP)
|
50
55
|
|
51
56
|
@http_sync ||= Net::HTTP.new(@uri.host, @uri.port)
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
response = @http_sync.post("#{@uri.path}?#{query.to_params}",
|
56
|
-
'Content-Type'=> 'application/json'
|
57
|
-
})
|
57
|
+
|
58
|
+
request = Pusher::Request.new(@uri, event_name, data, socket_id)
|
59
|
+
|
60
|
+
response = @http_sync.post("#{@uri.path}?#{request.query.to_params}",
|
61
|
+
request.body, { 'Content-Type'=> 'application/json' })
|
58
62
|
|
59
63
|
handle_response(response.code.to_i, response.body.chomp)
|
60
64
|
end
|
@@ -65,12 +69,15 @@ module Pusher
|
|
65
69
|
handle_error e
|
66
70
|
end
|
67
71
|
|
68
|
-
def
|
69
|
-
if
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
def socket_auth(socket_id)
|
73
|
+
raise "Invalid socket_id" if socket_id.nil? || socket_id.empty?
|
74
|
+
|
75
|
+
string_to_sign = "#{socket_id}:#{name}"
|
76
|
+
Pusher.logger.debug "Signing #{string_to_sign}"
|
77
|
+
token = Pusher.authentication_token
|
78
|
+
signature = HMAC::SHA256.hexdigest(token.secret, string_to_sign)
|
79
|
+
|
80
|
+
return "#{token.key}:#{signature}"
|
74
81
|
end
|
75
82
|
|
76
83
|
private
|
@@ -79,34 +86,7 @@ module Pusher
|
|
79
86
|
Pusher.logger.error("#{e.message} (#{e.class})")
|
80
87
|
Pusher.logger.debug(e.backtrace.join("\n"))
|
81
88
|
end
|
82
|
-
|
83
|
-
def construct_request(event_name, data, socket_id)
|
84
|
-
params = {
|
85
|
-
:name => event_name,
|
86
|
-
}
|
87
|
-
params[:socket_id] = socket_id if socket_id
|
88
|
-
|
89
|
-
body = case data
|
90
|
-
when String
|
91
|
-
data
|
92
|
-
else
|
93
|
-
begin
|
94
|
-
self.class.turn_into_json(data)
|
95
|
-
rescue => e
|
96
|
-
Pusher.logger.error("Could not convert #{data.inspect} into JSON")
|
97
|
-
raise e
|
98
|
-
end
|
99
|
-
end
|
100
|
-
params[:body_md5] = Digest::MD5.hexdigest(body)
|
101
89
|
|
102
|
-
request = Authentication::Request.new('POST', @uri.path, params)
|
103
|
-
auth_hash = request.sign(Pusher.authentication_token)
|
104
|
-
|
105
|
-
query_params = params.merge(auth_hash)
|
106
|
-
|
107
|
-
return query_params, body
|
108
|
-
end
|
109
|
-
|
110
90
|
def handle_response(status_code, body)
|
111
91
|
case status_code
|
112
92
|
when 202
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Pusher
|
2
|
+
class Request
|
3
|
+
attr_reader :body, :query
|
4
|
+
|
5
|
+
def initialize(resource, event_name, data, socket_id, token = nil)
|
6
|
+
params = {
|
7
|
+
:name => event_name,
|
8
|
+
}
|
9
|
+
params[:socket_id] = socket_id if socket_id
|
10
|
+
|
11
|
+
@body = case data
|
12
|
+
when String
|
13
|
+
data
|
14
|
+
else
|
15
|
+
begin
|
16
|
+
self.class.turn_into_json(data)
|
17
|
+
rescue => e
|
18
|
+
Pusher.logger.error("Could not convert #{data.inspect} into JSON")
|
19
|
+
raise e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
params[:body_md5] = Digest::MD5.hexdigest(body)
|
23
|
+
|
24
|
+
request = Signature::Request.new('POST', resource.path, params)
|
25
|
+
auth_hash = request.sign(token || Pusher.authentication_token)
|
26
|
+
|
27
|
+
@query = params.merge(auth_hash)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.turn_into_json(data)
|
31
|
+
if Object.const_defined?('ActiveSupport')
|
32
|
+
data.to_json
|
33
|
+
else
|
34
|
+
JSON.generate(data)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/pusher.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{pusher}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["New Bamboo"]
|
12
|
-
s.date = %q{2010-05-
|
12
|
+
s.date = %q{2010-05-14}
|
13
13
|
s.description = %q{Wrapper for pusherapp.com REST api}
|
14
14
|
s.email = %q{support@pusherapp.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -24,10 +24,9 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/pusher.rb",
|
27
|
-
"lib/pusher/authentication.rb",
|
28
27
|
"lib/pusher/channel.rb",
|
28
|
+
"lib/pusher/request.rb",
|
29
29
|
"pusher.gemspec",
|
30
|
-
"spec/authentication_spec.rb",
|
31
30
|
"spec/pusher_spec.rb",
|
32
31
|
"spec/spec.opts",
|
33
32
|
"spec/spec_helper.rb"
|
@@ -38,8 +37,7 @@ Gem::Specification.new do |s|
|
|
38
37
|
s.rubygems_version = %q{1.3.6}
|
39
38
|
s.summary = %q{Pusher App client}
|
40
39
|
s.test_files = [
|
41
|
-
"spec/
|
42
|
-
"spec/pusher_spec.rb",
|
40
|
+
"spec/pusher_spec.rb",
|
43
41
|
"spec/spec_helper.rb"
|
44
42
|
]
|
45
43
|
|
@@ -51,12 +49,14 @@ Gem::Specification.new do |s|
|
|
51
49
|
s.add_runtime_dependency(%q<json>, [">= 0"])
|
52
50
|
s.add_runtime_dependency(%q<crack>, [">= 0"])
|
53
51
|
s.add_runtime_dependency(%q<ruby-hmac>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<signature>, [">= 0"])
|
54
53
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
55
54
|
s.add_development_dependency(%q<webmock>, [">= 0"])
|
56
55
|
else
|
57
56
|
s.add_dependency(%q<json>, [">= 0"])
|
58
57
|
s.add_dependency(%q<crack>, [">= 0"])
|
59
58
|
s.add_dependency(%q<ruby-hmac>, [">= 0"])
|
59
|
+
s.add_dependency(%q<signature>, [">= 0"])
|
60
60
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
61
61
|
s.add_dependency(%q<webmock>, [">= 0"])
|
62
62
|
end
|
@@ -64,6 +64,7 @@ Gem::Specification.new do |s|
|
|
64
64
|
s.add_dependency(%q<json>, [">= 0"])
|
65
65
|
s.add_dependency(%q<crack>, [">= 0"])
|
66
66
|
s.add_dependency(%q<ruby-hmac>, [">= 0"])
|
67
|
+
s.add_dependency(%q<signature>, [">= 0"])
|
67
68
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
68
69
|
s.add_dependency(%q<webmock>, [">= 0"])
|
69
70
|
end
|
data/spec/pusher_spec.rb
CHANGED
@@ -169,7 +169,7 @@ describe Pusher do
|
|
169
169
|
end
|
170
170
|
end
|
171
171
|
|
172
|
-
describe "
|
172
|
+
describe "Channel#trigger_async" do
|
173
173
|
#in order to match URLs when testing http requests
|
174
174
|
#override the method that converts query hash to string
|
175
175
|
#to include a sort so URL is consistent
|
@@ -261,5 +261,25 @@ describe Pusher do
|
|
261
261
|
}
|
262
262
|
end
|
263
263
|
end
|
264
|
+
|
265
|
+
describe "Channel#socket_auth" do
|
266
|
+
before :each do
|
267
|
+
@channel = Pusher['test_channel']
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should return an authentication string given a socket id" do
|
271
|
+
auth = @channel.socket_auth('socketid')
|
272
|
+
|
273
|
+
auth.should == '12345678900000001:827076f551e22451357939e4c7bb1200de29f921d5bf80b40d71668f9cd61c40'
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should raise error if authentication is invalid" do
|
277
|
+
[nil, ''].each do |invalid|
|
278
|
+
lambda {
|
279
|
+
@channel.socket_auth(invalid)
|
280
|
+
}.should raise_error
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
264
284
|
end
|
265
285
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
version: 0.5.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- New Bamboo
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-14 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -54,9 +54,21 @@ dependencies:
|
|
54
54
|
type: :runtime
|
55
55
|
version_requirements: *id003
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: signature
|
58
58
|
prerelease: false
|
59
59
|
requirement: &id004 !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: *id004
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rspec
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
60
72
|
requirements:
|
61
73
|
- - ">="
|
62
74
|
- !ruby/object:Gem::Version
|
@@ -66,11 +78,11 @@ dependencies:
|
|
66
78
|
- 9
|
67
79
|
version: 1.2.9
|
68
80
|
type: :development
|
69
|
-
version_requirements: *
|
81
|
+
version_requirements: *id005
|
70
82
|
- !ruby/object:Gem::Dependency
|
71
83
|
name: webmock
|
72
84
|
prerelease: false
|
73
|
-
requirement: &
|
85
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
86
|
requirements:
|
75
87
|
- - ">="
|
76
88
|
- !ruby/object:Gem::Version
|
@@ -78,7 +90,7 @@ dependencies:
|
|
78
90
|
- 0
|
79
91
|
version: "0"
|
80
92
|
type: :development
|
81
|
-
version_requirements: *
|
93
|
+
version_requirements: *id006
|
82
94
|
description: Wrapper for pusherapp.com REST api
|
83
95
|
email: support@pusherapp.com
|
84
96
|
executables: []
|
@@ -96,10 +108,9 @@ files:
|
|
96
108
|
- Rakefile
|
97
109
|
- VERSION
|
98
110
|
- lib/pusher.rb
|
99
|
-
- lib/pusher/authentication.rb
|
100
111
|
- lib/pusher/channel.rb
|
112
|
+
- lib/pusher/request.rb
|
101
113
|
- pusher.gemspec
|
102
|
-
- spec/authentication_spec.rb
|
103
114
|
- spec/pusher_spec.rb
|
104
115
|
- spec/spec.opts
|
105
116
|
- spec/spec_helper.rb
|
@@ -134,6 +145,5 @@ signing_key:
|
|
134
145
|
specification_version: 3
|
135
146
|
summary: Pusher App client
|
136
147
|
test_files:
|
137
|
-
- spec/authentication_spec.rb
|
138
148
|
- spec/pusher_spec.rb
|
139
149
|
- spec/spec_helper.rb
|
@@ -1,142 +0,0 @@
|
|
1
|
-
require 'hmac-sha2'
|
2
|
-
require 'base64'
|
3
|
-
|
4
|
-
module Authentication
|
5
|
-
class AuthenticationError < RuntimeError; end
|
6
|
-
|
7
|
-
class Token
|
8
|
-
attr_reader :key, :secret
|
9
|
-
|
10
|
-
def initialize(key, secret)
|
11
|
-
@key, @secret = key, secret
|
12
|
-
end
|
13
|
-
|
14
|
-
def sign(request)
|
15
|
-
request.sign(self)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class Request
|
20
|
-
attr_accessor :path, :query_hash
|
21
|
-
|
22
|
-
# http://www.w3.org/TR/NOTE-datetime
|
23
|
-
ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
|
24
|
-
|
25
|
-
def initialize(method, path, query)
|
26
|
-
raise ArgumentError, "Expected string" unless path.kind_of?(String)
|
27
|
-
raise ArgumentError, "Expected hash" unless query.kind_of?(Hash)
|
28
|
-
|
29
|
-
query_hash = {}
|
30
|
-
auth_hash = {}
|
31
|
-
query.each do |key, v|
|
32
|
-
k = key.to_s.downcase
|
33
|
-
k[0..4] == 'auth_' ? auth_hash[k] = v : query_hash[k] = v
|
34
|
-
end
|
35
|
-
|
36
|
-
@method = method.upcase
|
37
|
-
@path, @query_hash, @auth_hash = path, query_hash, auth_hash
|
38
|
-
end
|
39
|
-
|
40
|
-
def sign(token)
|
41
|
-
@auth_hash = {
|
42
|
-
:auth_version => "1.0",
|
43
|
-
:auth_key => token.key,
|
44
|
-
:auth_timestamp => Time.now.to_i
|
45
|
-
}
|
46
|
-
|
47
|
-
@auth_hash[:auth_signature] = signature(token)
|
48
|
-
|
49
|
-
return @auth_hash
|
50
|
-
end
|
51
|
-
|
52
|
-
# Authenticates the request with a token
|
53
|
-
#
|
54
|
-
# Timestamp check: Unless timestamp_grace is set to nil (which will skip
|
55
|
-
# the timestamp check), an exception will be raised if timestamp is not
|
56
|
-
# supplied or if the timestamp provided is not within timestamp_grace of
|
57
|
-
# the real time (defaults to 10 minutes)
|
58
|
-
#
|
59
|
-
# Signature check: Raises an exception if the signature does not match the
|
60
|
-
# computed value
|
61
|
-
#
|
62
|
-
def authenticate_by_token!(token, timestamp_grace = 600)
|
63
|
-
validate_version!
|
64
|
-
validate_timestamp!(timestamp_grace)
|
65
|
-
validate_signature!(token)
|
66
|
-
true
|
67
|
-
end
|
68
|
-
|
69
|
-
def authenticate_by_token(token, timestamp_grace = 600)
|
70
|
-
authenticate_by_token!(token, timestamp_grace)
|
71
|
-
rescue AuthenticationError
|
72
|
-
false
|
73
|
-
end
|
74
|
-
|
75
|
-
def authenticate(timestamp_grace = 600, &block)
|
76
|
-
key = @auth_hash['auth_key']
|
77
|
-
raise AuthenticationError, "Authentication key required" unless key
|
78
|
-
token = yield key
|
79
|
-
unless token && token.secret
|
80
|
-
raise AuthenticationError, "Invalid authentication key"
|
81
|
-
end
|
82
|
-
authenticate_by_token!(token, timestamp_grace)
|
83
|
-
return token
|
84
|
-
end
|
85
|
-
|
86
|
-
def auth_hash
|
87
|
-
raise "Request not signed" unless @auth_hash && @auth_hash[:auth_signature]
|
88
|
-
@auth_hash
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def signature(token)
|
94
|
-
HMAC::SHA256.hexdigest(token.secret, string_to_sign)
|
95
|
-
end
|
96
|
-
|
97
|
-
def string_to_sign
|
98
|
-
[@method, @path, parameter_string].join("\n")
|
99
|
-
end
|
100
|
-
|
101
|
-
def parameter_string
|
102
|
-
param_hash = @query_hash.merge(@auth_hash || {})
|
103
|
-
|
104
|
-
# Convert keys to lowercase strings
|
105
|
-
hash = {}; param_hash.each { |k,v| hash[k.to_s.downcase] = v }
|
106
|
-
|
107
|
-
# Exclude signature from signature generation!
|
108
|
-
hash.delete("auth_signature")
|
109
|
-
|
110
|
-
hash.keys.sort.map { |k| "#{k}=#{hash[k]}" }.join("&")
|
111
|
-
end
|
112
|
-
|
113
|
-
def validate_version!
|
114
|
-
version = @auth_hash["auth_version"]
|
115
|
-
raise AuthenticationError, "Version required" unless version
|
116
|
-
raise AuthenticationError, "Version not supported" unless version == '1.0'
|
117
|
-
end
|
118
|
-
|
119
|
-
def validate_timestamp!(grace)
|
120
|
-
return true if grace.nil?
|
121
|
-
|
122
|
-
timestamp = @auth_hash["auth_timestamp"]
|
123
|
-
error = (timestamp.to_i - Time.now.to_i).abs
|
124
|
-
raise AuthenticationError, "Timestamp required" unless timestamp
|
125
|
-
if error >= grace
|
126
|
-
raise AuthenticationError, "Timestamp expired: Given timestamp "\
|
127
|
-
"(#{Time.at(timestamp.to_i).utc.strftime(ISO8601)}) "\
|
128
|
-
"not within #{grace}s of server time "\
|
129
|
-
"(#{Time.now.utc.strftime(ISO8601)})"
|
130
|
-
end
|
131
|
-
return true
|
132
|
-
end
|
133
|
-
|
134
|
-
def validate_signature!(token)
|
135
|
-
unless @auth_hash["auth_signature"] == signature(token)
|
136
|
-
raise AuthenticationError, "Invalid signature: you should have "\
|
137
|
-
"sent HmacSHA256Hex(#{string_to_sign.inspect}, your_secret_key)"
|
138
|
-
end
|
139
|
-
return true
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
data/spec/authentication_spec.rb
DELETED
@@ -1,176 +0,0 @@
|
|
1
|
-
require File.expand_path('../spec_helper', __FILE__)
|
2
|
-
|
3
|
-
describe Authentication do
|
4
|
-
before :each do
|
5
|
-
Time.stub!(:now).and_return(Time.at(1234))
|
6
|
-
|
7
|
-
@token = Authentication::Token.new('key', 'secret')
|
8
|
-
|
9
|
-
@request = Authentication::Request.new('POST', '/some/path', {
|
10
|
-
"query" => "params",
|
11
|
-
"go" => "here"
|
12
|
-
})
|
13
|
-
@signature = @request.sign(@token)[:auth_signature]
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should generate base64 encoded signature from correct key" do
|
17
|
-
@request.send(:string_to_sign).should == "POST\n/some/path\nauth_key=key&auth_timestamp=1234&auth_version=1.0&go=here&query=params"
|
18
|
-
@signature.should == '3b237953a5ba6619875cbb2a2d43e8da9ef5824e8a2c689f6284ac85bc1ea0db'
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should make auth_hash available after request is signed" do
|
22
|
-
request = Authentication::Request.new('POST', '/some/path', {
|
23
|
-
"query" => "params"
|
24
|
-
})
|
25
|
-
lambda {
|
26
|
-
request.auth_hash
|
27
|
-
}.should raise_error('Request not signed')
|
28
|
-
|
29
|
-
request.sign(@token)
|
30
|
-
request.auth_hash.should == {
|
31
|
-
:auth_signature => "da078fcedd72941b6c873caa40d0d6b2000ebfc700cee802b128dd20f72e74e9",
|
32
|
-
:auth_version => "1.0",
|
33
|
-
:auth_key => "key",
|
34
|
-
:auth_timestamp => 1234
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
it "should cope with symbol keys" do
|
39
|
-
@request.query_hash = {
|
40
|
-
:query => "params",
|
41
|
-
:go => "here"
|
42
|
-
}
|
43
|
-
@request.sign(@token)[:auth_signature].should == @signature
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should cope with upcase keys (keys are lowercased before signing)" do
|
47
|
-
@request.query_hash = {
|
48
|
-
"Query" => "params",
|
49
|
-
"GO" => "here"
|
50
|
-
}
|
51
|
-
@request.sign(@token)[:auth_signature].should == @signature
|
52
|
-
end
|
53
|
-
|
54
|
-
it "should use the path to generate signature" do
|
55
|
-
@request.path = '/some/other/path'
|
56
|
-
@request.sign(@token)[:auth_signature].should_not == @signature
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should use the query string keys to generate signature" do
|
60
|
-
@request.query_hash = {
|
61
|
-
"other" => "query"
|
62
|
-
}
|
63
|
-
@request.sign(@token)[:auth_signature].should_not == @signature
|
64
|
-
end
|
65
|
-
|
66
|
-
it "should use the query string values to generate signature" do
|
67
|
-
@request.query_hash = {
|
68
|
-
"key" => "notfoo",
|
69
|
-
"other" => 'bar'
|
70
|
-
}
|
71
|
-
@request.sign(@token)[:signature].should_not == @signature
|
72
|
-
end
|
73
|
-
|
74
|
-
describe "verification" do
|
75
|
-
before :each do
|
76
|
-
Time.stub!(:now).and_return(Time.at(1234))
|
77
|
-
@request.sign(@token)
|
78
|
-
@params = @request.query_hash.merge(@request.auth_hash)
|
79
|
-
end
|
80
|
-
|
81
|
-
it "should verify requests" do
|
82
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
83
|
-
request.authenticate_by_token(@token).should == true
|
84
|
-
end
|
85
|
-
|
86
|
-
it "should raise error if signature is not correct" do
|
87
|
-
@params[:auth_signature] = 'asdf'
|
88
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
89
|
-
lambda {
|
90
|
-
request.authenticate_by_token!(@token)
|
91
|
-
}.should raise_error('Invalid signature: you should have sent HmacSHA256Hex("POST\n/some/path\nauth_key=key&auth_timestamp=1234&auth_version=1.0&go=here&query=params", your_secret_key)')
|
92
|
-
end
|
93
|
-
|
94
|
-
it "should raise error if timestamp not available" do
|
95
|
-
@params.delete(:auth_timestamp)
|
96
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
97
|
-
lambda {
|
98
|
-
request.authenticate_by_token!(@token)
|
99
|
-
}.should raise_error('Timestamp required')
|
100
|
-
end
|
101
|
-
|
102
|
-
it "should raise error if timestamp has expired (default of 600s)" do
|
103
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
104
|
-
Time.stub!(:now).and_return(Time.at(1234 + 599))
|
105
|
-
request.authenticate_by_token!(@token).should == true
|
106
|
-
Time.stub!(:now).and_return(Time.at(1234 - 599))
|
107
|
-
request.authenticate_by_token!(@token).should == true
|
108
|
-
Time.stub!(:now).and_return(Time.at(1234 + 600))
|
109
|
-
lambda {
|
110
|
-
request.authenticate_by_token!(@token)
|
111
|
-
}.should raise_error("Timestamp expired: Given timestamp (1970-01-01T00:20:34Z) not within 600s of server time (1970-01-01T00:30:34Z)")
|
112
|
-
Time.stub!(:now).and_return(Time.at(1234 - 600))
|
113
|
-
lambda {
|
114
|
-
request.authenticate_by_token!(@token)
|
115
|
-
}.should raise_error("Timestamp expired: Given timestamp (1970-01-01T00:20:34Z) not within 600s of server time (1970-01-01T00:10:34Z)")
|
116
|
-
end
|
117
|
-
|
118
|
-
it "should be possible to customize the timeout grace period" do
|
119
|
-
grace = 10
|
120
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
121
|
-
Time.stub!(:now).and_return(Time.at(1234 + grace - 1))
|
122
|
-
request.authenticate_by_token!(@token, grace).should == true
|
123
|
-
Time.stub!(:now).and_return(Time.at(1234 + grace))
|
124
|
-
lambda {
|
125
|
-
request.authenticate_by_token!(@token, grace)
|
126
|
-
}.should raise_error("Timestamp expired: Given timestamp (1970-01-01T00:20:34Z) not within 10s of server time (1970-01-01T00:20:44Z)")
|
127
|
-
end
|
128
|
-
|
129
|
-
it "should be possible to skip timestamp check by passing nil" do
|
130
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
131
|
-
Time.stub!(:now).and_return(Time.at(1234 + 1000))
|
132
|
-
request.authenticate_by_token!(@token, nil).should == true
|
133
|
-
end
|
134
|
-
|
135
|
-
it "should check that auth_version is supplied" do
|
136
|
-
@params.delete(:auth_version)
|
137
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
138
|
-
lambda {
|
139
|
-
request.authenticate_by_token!(@token)
|
140
|
-
}.should raise_error('Version required')
|
141
|
-
end
|
142
|
-
|
143
|
-
it "should check that auth_version equals 1.0" do
|
144
|
-
@params[:auth_version] = '1.1'
|
145
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
146
|
-
lambda {
|
147
|
-
request.authenticate_by_token!(@token)
|
148
|
-
}.should raise_error('Version not supported')
|
149
|
-
end
|
150
|
-
|
151
|
-
describe "when used with optional block" do
|
152
|
-
it "should optionally take a block which yields the signature" do
|
153
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
154
|
-
request.authenticate do |key|
|
155
|
-
key.should == @token.key
|
156
|
-
@token
|
157
|
-
end.should == @token
|
158
|
-
end
|
159
|
-
|
160
|
-
it "should raise error if no auth_key supplied to request" do
|
161
|
-
@params.delete(:auth_key)
|
162
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
163
|
-
lambda {
|
164
|
-
request.authenticate { |key| nil }
|
165
|
-
}.should raise_error('Authentication key required')
|
166
|
-
end
|
167
|
-
|
168
|
-
it "should raise error if block returns nil (i.e. key doesn't exist)" do
|
169
|
-
request = Authentication::Request.new('POST', '/some/path', @params)
|
170
|
-
lambda {
|
171
|
-
request.authenticate { |key| nil }
|
172
|
-
}.should raise_error('Invalid authentication key')
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|