pusher 0.3.5 → 0.4.0.beta
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 +8 -3
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/lib/pusher.rb +11 -9
- data/lib/pusher/authentication.rb +144 -0
- data/lib/pusher/channel.rb +53 -15
- data/pusher.gemspec +13 -4
- data/spec/authentication_spec.rb +176 -0
- data/spec/pusher_spec.rb +90 -30
- metadata +40 -10
data/README.md
CHANGED
@@ -6,13 +6,18 @@ Getting started
|
|
6
6
|
|
7
7
|
After registering at <http://pusherapp.com> configure your app with the security credentials
|
8
8
|
|
9
|
+
Pusher.app_id = 'your-pusher-app-id'
|
9
10
|
Pusher.key = 'your-pusher-key'
|
10
11
|
Pusher.secret = 'your-pusher-secret'
|
11
12
|
|
12
|
-
Trigger an event
|
13
|
+
Trigger an event. Channel and event names may only contain alphanumeric characters, '-' and '_'.
|
14
|
+
|
15
|
+
Pusher['a_channel'].trigger('an_event', {:some => 'data'})
|
16
|
+
|
17
|
+
Optionally a socket id may be provided. This will prevent the event from being triggered on this specific socket id (see <http://pusherapp.com/docs/duplicates> for more info).
|
18
|
+
|
19
|
+
Pusher['a_channel'].trigger('an_event', {:some => 'data'}, socket_id)
|
13
20
|
|
14
|
-
Pusher['arbitrary-channel-name'].trigger({:some => 'data'})
|
15
|
-
|
16
21
|
Logging
|
17
22
|
-------
|
18
23
|
|
data/Rakefile
CHANGED
@@ -12,7 +12,9 @@ begin
|
|
12
12
|
gem.authors = ["New Bamboo"]
|
13
13
|
# gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
14
|
gem.add_dependency "json"
|
15
|
+
gem.add_dependency "crack"
|
15
16
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
17
|
+
gem.add_development_dependency "webmock"
|
16
18
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
19
|
end
|
18
20
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0.beta
|
data/lib/pusher.rb
CHANGED
@@ -5,16 +5,13 @@ require 'net/http'
|
|
5
5
|
autoload 'Logger', 'logger'
|
6
6
|
|
7
7
|
module Pusher
|
8
|
-
class
|
9
|
-
|
10
|
-
'You must configure both Pusher.key in order to authenticate your Pusher app'
|
11
|
-
end
|
12
|
-
end
|
8
|
+
class Error < RuntimeError; end
|
9
|
+
class AuthenticationError < Error; end
|
13
10
|
|
14
11
|
class << self
|
15
12
|
attr_accessor :host, :port
|
16
13
|
attr_writer :logger
|
17
|
-
attr_accessor :key, :secret
|
14
|
+
attr_accessor :app_id, :key, :secret
|
18
15
|
|
19
16
|
def logger
|
20
17
|
@logger ||= begin
|
@@ -23,16 +20,21 @@ module Pusher
|
|
23
20
|
log
|
24
21
|
end
|
25
22
|
end
|
23
|
+
|
24
|
+
def authentication_token
|
25
|
+
Authentication::Token.new(@key, @secret)
|
26
|
+
end
|
26
27
|
end
|
27
28
|
|
28
29
|
self.host = 'api.pusherapp.com'
|
29
30
|
self.port = 80
|
30
31
|
|
31
|
-
def self.[](
|
32
|
-
raise ArgumentError unless @key
|
32
|
+
def self.[](channel_name)
|
33
|
+
raise ArgumentError, 'Missing configuration: please check that Pusher.app_id, Pusher.key, and Pusher.secret are all configured' unless @app_id && @key && @secret
|
33
34
|
@channels ||= {}
|
34
|
-
@channels[
|
35
|
+
@channels[channel_name.to_s] = Channel.new(@app_id, channel_name)
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
39
|
require 'pusher/channel'
|
40
|
+
require 'pusher/authentication'
|
@@ -0,0 +1,144 @@
|
|
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
|
+
hmac_signature = HMAC::SHA256.digest(token.secret, string_to_sign)
|
48
|
+
# chomp because the Base64 output ends with \n
|
49
|
+
@auth_hash[:auth_signature] = Base64.encode64(hmac_signature).chomp
|
50
|
+
|
51
|
+
return @auth_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
# Authenticates the request with a token
|
55
|
+
#
|
56
|
+
# Timestamp check: Unless timestamp_grace is set to nil (which will skip
|
57
|
+
# the timestamp check), an exception will be raised if timestamp is not
|
58
|
+
# supplied or if the timestamp provided is not within timestamp_grace of
|
59
|
+
# the real time (defaults to 10 minutes)
|
60
|
+
#
|
61
|
+
# Signature check: Raises an exception if the signature does not match the
|
62
|
+
# computed value
|
63
|
+
#
|
64
|
+
def authenticate_by_token!(token, timestamp_grace = 600)
|
65
|
+
validate_version!
|
66
|
+
validate_timestamp!(timestamp_grace)
|
67
|
+
validate_signature!(token)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def authenticate_by_token(token, timestamp_grace = 600)
|
72
|
+
authenticate_by_token!(token, timestamp_grace)
|
73
|
+
rescue AuthenticationError
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def authenticate(timestamp_grace = 600, &block)
|
78
|
+
key = @auth_hash['auth_key']
|
79
|
+
raise AuthenticationError, "Authentication key required" unless key
|
80
|
+
token = yield key
|
81
|
+
unless token && token.secret
|
82
|
+
raise AuthenticationError, "Invalid authentication key"
|
83
|
+
end
|
84
|
+
authenticate_by_token!(token, timestamp_grace)
|
85
|
+
return token
|
86
|
+
end
|
87
|
+
|
88
|
+
def auth_hash
|
89
|
+
raise "Request not signed" unless @auth_hash && @auth_hash[:auth_signature]
|
90
|
+
@auth_hash
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def string_to_sign
|
96
|
+
[@method, @path, parameter_string].join("\n")
|
97
|
+
end
|
98
|
+
|
99
|
+
def parameter_string
|
100
|
+
param_hash = @query_hash.merge(@auth_hash || {})
|
101
|
+
|
102
|
+
# Convert keys to lowercase strings
|
103
|
+
hash = {}; param_hash.each { |k,v| hash[k.to_s.downcase] = v }
|
104
|
+
|
105
|
+
# Exclude signature from signature generation!
|
106
|
+
hash.delete("auth_signature")
|
107
|
+
|
108
|
+
hash.keys.sort.map { |k| "#{k}=#{hash[k]}" }.join("&")
|
109
|
+
end
|
110
|
+
|
111
|
+
def validate_version!
|
112
|
+
version = @auth_hash["auth_version"]
|
113
|
+
raise AuthenticationError, "Version required" unless version
|
114
|
+
raise AuthenticationError, "Version not supported" unless version == '1.0'
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_timestamp!(grace)
|
118
|
+
return true if grace.nil?
|
119
|
+
|
120
|
+
timestamp = @auth_hash["auth_timestamp"]
|
121
|
+
error = (timestamp.to_i - Time.now.to_i).abs
|
122
|
+
raise AuthenticationError, "Timestamp required" unless timestamp
|
123
|
+
if error >= grace
|
124
|
+
raise AuthenticationError, "Timestamp expired: Given timestamp "\
|
125
|
+
"(#{Time.at(timestamp.to_i).utc.strftime(ISO8601)}) "\
|
126
|
+
"not within #{grace}s of server time "\
|
127
|
+
"(#{Time.now.utc.strftime(ISO8601)})"
|
128
|
+
end
|
129
|
+
return true
|
130
|
+
end
|
131
|
+
|
132
|
+
def validate_signature!(token)
|
133
|
+
string = string_to_sign
|
134
|
+
hmac_signature = HMAC::SHA256.digest(token.secret, string)
|
135
|
+
# chomp because the Base64 output ends with \n
|
136
|
+
base64_signature = Base64.encode64(hmac_signature).chomp
|
137
|
+
unless @auth_hash["auth_signature"] == base64_signature
|
138
|
+
raise AuthenticationError, "Invalid signature: you should have "\
|
139
|
+
"sent Base64Encode(HmacSHA256(#{string.inspect}, your_secret_key))"
|
140
|
+
end
|
141
|
+
return true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/pusher/channel.rb
CHANGED
@@ -1,24 +1,62 @@
|
|
1
|
+
require 'crack/core_extensions' # Used for Hash#to_params
|
2
|
+
require 'digest/md5'
|
3
|
+
|
1
4
|
module Pusher
|
2
5
|
class Channel
|
3
|
-
def initialize(
|
4
|
-
@uri = URI.
|
6
|
+
def initialize(app_id, name)
|
7
|
+
@uri = URI::HTTP.build({
|
8
|
+
:host => Pusher.host,
|
9
|
+
:port => Pusher.port,
|
10
|
+
:path => "/apps/#{app_id}/channels/#{name}/events"
|
11
|
+
})
|
5
12
|
@http = Net::HTTP.new(@uri.host, @uri.port)
|
6
13
|
end
|
7
14
|
|
8
|
-
def trigger(event_name, data, socket_id = nil)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
def trigger!(event_name, data, socket_id = nil)
|
16
|
+
params = {
|
17
|
+
:name => event_name,
|
18
|
+
}
|
19
|
+
params[:socket_id] = socket_id if socket_id
|
20
|
+
|
21
|
+
body = case data
|
22
|
+
when String
|
23
|
+
data
|
24
|
+
else
|
25
|
+
begin
|
26
|
+
self.class.turn_into_json(data)
|
27
|
+
rescue => e
|
28
|
+
Pusher.logger.error("Could not convert #{data.inspect} into JSON")
|
29
|
+
raise e
|
30
|
+
end
|
21
31
|
end
|
32
|
+
params[:body_md5] = Digest::MD5.hexdigest(body)
|
33
|
+
|
34
|
+
request = Authentication::Request.new('POST', @uri.path, params)
|
35
|
+
auth_hash = request.sign(Pusher.authentication_token)
|
36
|
+
|
37
|
+
query_params = params.merge(auth_hash)
|
38
|
+
@uri.query = query_params.to_params
|
39
|
+
|
40
|
+
response = @http.post("#{@uri.path}?#{@uri.query}", body, {
|
41
|
+
'Content-Type'=> 'application/json'
|
42
|
+
})
|
43
|
+
|
44
|
+
case response.code
|
45
|
+
when "202"
|
46
|
+
return true
|
47
|
+
when "401"
|
48
|
+
raise AuthenticationError, response.body.chomp
|
49
|
+
when "404"
|
50
|
+
raise Error, "Resource not found: app_id is probably invalid"
|
51
|
+
else
|
52
|
+
raise Error, "Unknown error in Pusher: #{response.body.chomp}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def trigger(event_name, data, socket_id = nil)
|
57
|
+
trigger!(event_name, data, socket_id)
|
58
|
+
rescue StandardError => e
|
59
|
+
handle_error e
|
22
60
|
end
|
23
61
|
|
24
62
|
def self.turn_into_json(data)
|
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.4.0.beta"
|
9
9
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["New Bamboo"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-22}
|
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,8 +24,10 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/pusher.rb",
|
27
|
+
"lib/pusher/authentication.rb",
|
27
28
|
"lib/pusher/channel.rb",
|
28
29
|
"pusher.gemspec",
|
30
|
+
"spec/authentication_spec.rb",
|
29
31
|
"spec/pusher_spec.rb",
|
30
32
|
"spec/spec.opts",
|
31
33
|
"spec/spec_helper.rb"
|
@@ -36,7 +38,8 @@ Gem::Specification.new do |s|
|
|
36
38
|
s.rubygems_version = %q{1.3.6}
|
37
39
|
s.summary = %q{Pusher App client}
|
38
40
|
s.test_files = [
|
39
|
-
"spec/
|
41
|
+
"spec/authentication_spec.rb",
|
42
|
+
"spec/pusher_spec.rb",
|
40
43
|
"spec/spec_helper.rb"
|
41
44
|
]
|
42
45
|
|
@@ -46,14 +49,20 @@ Gem::Specification.new do |s|
|
|
46
49
|
|
47
50
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
48
51
|
s.add_runtime_dependency(%q<json>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<crack>, [">= 0"])
|
49
53
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
54
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
50
55
|
else
|
51
56
|
s.add_dependency(%q<json>, [">= 0"])
|
57
|
+
s.add_dependency(%q<crack>, [">= 0"])
|
52
58
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
59
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
53
60
|
end
|
54
61
|
else
|
55
62
|
s.add_dependency(%q<json>, [">= 0"])
|
63
|
+
s.add_dependency(%q<crack>, [">= 0"])
|
56
64
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
65
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
@@ -0,0 +1,176 @@
|
|
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 == 'OyN5U6W6ZhmHXLsqLUPo2p71gk6KLGifYoSshbweoNs='
|
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 => "2gePzt1ylBtshzyqQNDWsgAOv8cAzugCsSjdIPcudOk=",
|
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 Base64Encode(HmacSHA256("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
|
data/spec/pusher_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
3
5
|
describe Pusher do
|
4
6
|
describe 'configuration' do
|
5
7
|
it 'should be preconfigured for api host' do
|
@@ -22,27 +24,17 @@ describe Pusher do
|
|
22
24
|
Pusher.logger.debug('foo')
|
23
25
|
Pusher.logger = nil
|
24
26
|
end
|
25
|
-
|
26
|
-
it 'should raise exception if key and secret are missing' do
|
27
|
-
lambda {
|
28
|
-
Pusher['test-channel']
|
29
|
-
}.should raise_error(Pusher::ArgumentError)
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'should raise exception if key is missing' do
|
33
|
-
lambda {
|
34
|
-
Pusher['test-channel']
|
35
|
-
}.should raise_error(Pusher::ArgumentError)
|
36
|
-
end
|
37
27
|
end
|
38
28
|
|
39
29
|
describe 'configured' do
|
40
30
|
before do
|
31
|
+
Pusher.app_id = '20'
|
41
32
|
Pusher.key = '12345678900000001'
|
42
33
|
Pusher.secret = '12345678900000001'
|
43
34
|
end
|
44
35
|
|
45
36
|
after do
|
37
|
+
Pusher.app_id = nil
|
46
38
|
Pusher.key = nil
|
47
39
|
Pusher.secret = nil
|
48
40
|
end
|
@@ -55,34 +47,102 @@ describe Pusher do
|
|
55
47
|
it 'should return a new channel' do
|
56
48
|
@channel.should be_kind_of(Pusher::Channel)
|
57
49
|
end
|
50
|
+
|
51
|
+
%w{app_id key secret}.each do |config|
|
52
|
+
it "should raise exception if #{config} not configured" do
|
53
|
+
Pusher.send("#{config}=", nil)
|
54
|
+
lambda {
|
55
|
+
Pusher['test_channel']
|
56
|
+
}.should raise_error(ArgumentError)
|
57
|
+
end
|
58
|
+
end
|
58
59
|
end
|
59
60
|
|
60
|
-
describe 'Channel#trigger' do
|
61
|
-
before do
|
62
|
-
|
63
|
-
|
61
|
+
describe 'Channel#trigger!' do
|
62
|
+
before :each do
|
63
|
+
WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event})
|
64
|
+
@channel = Pusher['test_channel']
|
64
65
|
end
|
65
66
|
|
66
67
|
it 'should configure HTTP library to talk to pusher API' do
|
67
|
-
|
68
|
-
|
69
|
-
Pusher['test_channel'].trigger('new_event', 'Some data')
|
68
|
+
@channel.trigger!('new_event', 'Some data')
|
69
|
+
WebMock.request(:post, %r{api.pusherapp.com}).should have_been_made
|
70
70
|
end
|
71
71
|
|
72
|
-
it 'should POST
|
73
|
-
@
|
74
|
-
path.should == '/app/12345678900000001/channel/test_channel'
|
75
|
-
parsed = JSON.parse(data)
|
76
|
-
parsed['event'].should == 'new_event'
|
77
|
-
parsed['data']['name'].should == 'Pusher'
|
78
|
-
parsed['data']['last_name'].should == 'App'
|
79
|
-
parsed['socket_id'].should == nil
|
80
|
-
headers.should == {'Content-Type'=> 'application/json'}
|
81
|
-
end
|
82
|
-
Pusher['test_channel'].trigger('new_event', {
|
72
|
+
it 'should POST with the correct parameters and convert data to JSON' do
|
73
|
+
@channel.trigger!('new_event', {
|
83
74
|
:name => 'Pusher',
|
84
75
|
:last_name => 'App'
|
85
76
|
})
|
77
|
+
WebMock.request(:post, %r{/app/20/channel/test_channel/event}).
|
78
|
+
with do |req|
|
79
|
+
|
80
|
+
query_hash = req.uri.query_values
|
81
|
+
query_hash["name"].should == 'new_event'
|
82
|
+
query_hash["auth_key"].should == Pusher.key
|
83
|
+
query_hash["auth_timestamp"].should_not be_nil
|
84
|
+
|
85
|
+
parsed = JSON.parse(req.body)
|
86
|
+
parsed.should == {
|
87
|
+
"name" => 'Pusher',
|
88
|
+
"last_name" => 'App'
|
89
|
+
}
|
90
|
+
|
91
|
+
req.headers['Content-Type'].should == 'application/json'
|
92
|
+
end.should have_been_made
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should handle string data by sending unmodified in body" do
|
96
|
+
string = "foo\nbar\""
|
97
|
+
@channel.trigger!('new_event', string)
|
98
|
+
WebMock.request(:post, %r{/app/20/channel/test_channel/event}).with do |req|
|
99
|
+
req.body.should == "foo\nbar\""
|
100
|
+
end.should have_been_made
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should raise error if an object sent which canot be JSONified" do
|
104
|
+
lambda {
|
105
|
+
@channel.trigger!('new_event', Object.new)
|
106
|
+
}.should raise_error(JSON::GeneratorError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should propagate exception if exception raised" do
|
110
|
+
WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
|
111
|
+
to_raise(RuntimeError)
|
112
|
+
lambda {
|
113
|
+
Pusher['test_channel'].trigger!('new_event', 'Some data')
|
114
|
+
}.should raise_error(RuntimeError)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should raise AuthenticationError if pusher returns 401" do
|
118
|
+
WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
|
119
|
+
to_return(:status => 401)
|
120
|
+
lambda {
|
121
|
+
Pusher['test_channel'].trigger!('new_event', 'Some data')
|
122
|
+
}.should raise_error(Pusher::AuthenticationError)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should raise Pusher::Error if pusher returns 404" do
|
126
|
+
WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
|
127
|
+
to_return(:status => 404)
|
128
|
+
lambda {
|
129
|
+
Pusher['test_channel'].trigger!('new_event', 'Some data')
|
130
|
+
}.should raise_error(Pusher::Error, 'Resource not found: app_id is probably invalid')
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should raise Pusher::Error if pusher returns 500" do
|
134
|
+
WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
|
135
|
+
to_return(:status => 500, :body => "some error")
|
136
|
+
lambda {
|
137
|
+
Pusher['test_channel'].trigger!('new_event', 'Some data')
|
138
|
+
}.should raise_error(Pusher::Error, 'Unknown error in Pusher: some error')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'Channel#trigger' do
|
143
|
+
before :each do
|
144
|
+
@http = mock('HTTP', :post => 'posting')
|
145
|
+
Net::HTTP.stub!(:new).and_return @http
|
86
146
|
end
|
87
147
|
|
88
148
|
it "should log failure if exception raised" do
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pusher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
4
|
+
prerelease: true
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
|
7
|
+
- 4
|
8
|
+
- 0
|
9
|
+
- beta
|
10
|
+
version: 0.4.0.beta
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- New Bamboo
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-04-
|
18
|
+
date: 2010-04-22 00:00:00 +01:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
@@ -30,9 +31,21 @@ dependencies:
|
|
30
31
|
type: :runtime
|
31
32
|
version_requirements: *id001
|
32
33
|
- !ruby/object:Gem::Dependency
|
33
|
-
name:
|
34
|
+
name: crack
|
34
35
|
prerelease: false
|
35
36
|
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
43
|
+
type: :runtime
|
44
|
+
version_requirements: *id002
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rspec
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
36
49
|
requirements:
|
37
50
|
- - ">="
|
38
51
|
- !ruby/object:Gem::Version
|
@@ -42,7 +55,19 @@ dependencies:
|
|
42
55
|
- 9
|
43
56
|
version: 1.2.9
|
44
57
|
type: :development
|
45
|
-
version_requirements: *
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: webmock
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
type: :development
|
70
|
+
version_requirements: *id004
|
46
71
|
description: Wrapper for pusherapp.com REST api
|
47
72
|
email: support@pusherapp.com
|
48
73
|
executables: []
|
@@ -60,8 +85,10 @@ files:
|
|
60
85
|
- Rakefile
|
61
86
|
- VERSION
|
62
87
|
- lib/pusher.rb
|
88
|
+
- lib/pusher/authentication.rb
|
63
89
|
- lib/pusher/channel.rb
|
64
90
|
- pusher.gemspec
|
91
|
+
- spec/authentication_spec.rb
|
65
92
|
- spec/pusher_spec.rb
|
66
93
|
- spec/spec.opts
|
67
94
|
- spec/spec_helper.rb
|
@@ -83,11 +110,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
110
|
version: "0"
|
84
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
112
|
requirements:
|
86
|
-
- - "
|
113
|
+
- - ">"
|
87
114
|
- !ruby/object:Gem::Version
|
88
115
|
segments:
|
89
|
-
-
|
90
|
-
|
116
|
+
- 1
|
117
|
+
- 3
|
118
|
+
- 1
|
119
|
+
version: 1.3.1
|
91
120
|
requirements: []
|
92
121
|
|
93
122
|
rubyforge_project:
|
@@ -96,5 +125,6 @@ signing_key:
|
|
96
125
|
specification_version: 3
|
97
126
|
summary: Pusher App client
|
98
127
|
test_files:
|
128
|
+
- spec/authentication_spec.rb
|
99
129
|
- spec/pusher_spec.rb
|
100
130
|
- spec/spec_helper.rb
|