pusher 1.3.1 → 1.4.2
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.
- checksums.yaml +5 -13
- data/.github/stale.yml +26 -0
- data/.travis.yml +6 -5
- data/CHANGELOG.md +31 -1
- data/README.md +104 -119
- data/examples/presence_channels/presence_channels.rb +56 -0
- data/examples/presence_channels/public/presence_channels.html +28 -0
- data/lib/pusher.rb +1 -1
- data/lib/pusher/channel.rb +14 -2
- data/lib/pusher/client.rb +75 -5
- data/lib/pusher/request.rb +2 -0
- data/lib/pusher/version.rb +1 -1
- data/pusher.gemspec +12 -11
- data/spec/channel_spec.rb +25 -4
- data/spec/client_spec.rb +176 -5
- metadata +66 -50
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'sinatra'
|
|
2
|
+
require 'sinatra/cookies'
|
|
3
|
+
require 'sinatra/json'
|
|
4
|
+
require 'pusher'
|
|
5
|
+
|
|
6
|
+
# You can get these variables from http://dashboard.pusher.com
|
|
7
|
+
pusher = Pusher::Client.new(
|
|
8
|
+
app_id: 'your-app-id',
|
|
9
|
+
key: 'your-app-key',
|
|
10
|
+
secret: 'your-app-secret',
|
|
11
|
+
cluster: 'your-app-cluster'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
set :public_folder, 'public'
|
|
15
|
+
|
|
16
|
+
get "/" do
|
|
17
|
+
redirect '/presence_channels.html'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Emulate rails behaviour where this information would be stored in session
|
|
21
|
+
get '/signin' do
|
|
22
|
+
cookies[:user_id] = 'example_cookie'
|
|
23
|
+
'Ok'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Auth endpoint: https://pusher.com/docs/channels/server_api/authenticating-users
|
|
27
|
+
post '/pusher/auth' do
|
|
28
|
+
channel_data = {
|
|
29
|
+
user_id: 'example_user',
|
|
30
|
+
user_info: {
|
|
31
|
+
name: 'example_name',
|
|
32
|
+
email: 'example_email'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if cookies[:user_id] == 'example_cookie'
|
|
37
|
+
response = pusher.authenticate(params[:channel_name], params[:socket_id], channel_data)
|
|
38
|
+
json response
|
|
39
|
+
else
|
|
40
|
+
status 403
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
get '/pusher_trigger' do
|
|
45
|
+
channels = ['presence-channel-test'];
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
pusher.trigger(channels, 'test-event', {
|
|
49
|
+
message: 'hello world'
|
|
50
|
+
})
|
|
51
|
+
rescue Pusher::Error => e
|
|
52
|
+
# (Pusher::AuthenticationError, Pusher::HTTPError, or Pusher::Error)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
'Triggered!'
|
|
56
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Pusher Test</title>
|
|
4
|
+
<script src="https://js.pusher.com/5.0/pusher.min.js"></script>
|
|
5
|
+
<script>
|
|
6
|
+
|
|
7
|
+
// Enable pusher logging - don't include this in production
|
|
8
|
+
Pusher.logToConsole = true;
|
|
9
|
+
|
|
10
|
+
var pusher = new Pusher('your-app-key', {
|
|
11
|
+
cluster: 'your-app-cluster',
|
|
12
|
+
forceTLS: true,
|
|
13
|
+
authEndpoint: '/pusher/auth'
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
var channel = pusher.subscribe('presence-channel-test');
|
|
17
|
+
channel.bind('test-event', function(data) {
|
|
18
|
+
alert(JSON.stringify(data));
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<h1>Pusher Test</h1>
|
|
24
|
+
<p>
|
|
25
|
+
Try publishing an event to channel <code>presence-channel-test</code>
|
|
26
|
+
with event name <code>test-event</code>.
|
|
27
|
+
</p>
|
|
28
|
+
</body>
|
data/lib/pusher.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Pusher
|
|
|
32
32
|
def_delegators :default_client, :scheme=, :host=, :port=, :app_id=, :key=, :secret=, :http_proxy=
|
|
33
33
|
def_delegators :default_client, :notification_host=, :notification_scheme=
|
|
34
34
|
|
|
35
|
-
def_delegators :default_client, :authentication_token, :url
|
|
35
|
+
def_delegators :default_client, :authentication_token, :url, :cluster
|
|
36
36
|
def_delegators :default_client, :encrypted=, :url=, :cluster=
|
|
37
37
|
def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=, :keep_alive_timeout=
|
|
38
38
|
|
data/lib/pusher/channel.rb
CHANGED
|
@@ -86,6 +86,9 @@ module Pusher
|
|
|
86
86
|
|
|
87
87
|
# Request info for a channel
|
|
88
88
|
#
|
|
89
|
+
# @example Response
|
|
90
|
+
# [{:occupied=>true, :subscription_count => 12}]
|
|
91
|
+
#
|
|
89
92
|
# @param info [Array] Array of attributes required (as lowercase strings)
|
|
90
93
|
# @return [Hash] Hash of requested attributes for this channel
|
|
91
94
|
# @raise [Pusher::Error] on invalid Pusher response - see the error message for more details
|
|
@@ -99,7 +102,7 @@ module Pusher
|
|
|
99
102
|
# Only works on presence channels (see: http://pusher.com/docs/client_api_guide/client_presence_channels and https://pusher.com/docs/rest_api)
|
|
100
103
|
#
|
|
101
104
|
# @example Response
|
|
102
|
-
# [{
|
|
105
|
+
# [{:id=>"4"}]
|
|
103
106
|
#
|
|
104
107
|
# @param params [Hash] Hash of parameters for the API - see REST API docs
|
|
105
108
|
# @return [Hash] Array of user hashes for this channel
|
|
@@ -146,7 +149,7 @@ module Pusher
|
|
|
146
149
|
# render :json => Pusher['private-my_channel'].authenticate(params[:socket_id])
|
|
147
150
|
#
|
|
148
151
|
# @example Presence channels
|
|
149
|
-
# render :json => Pusher['
|
|
152
|
+
# render :json => Pusher['presence-my_channel'].authenticate(params[:socket_id], {
|
|
150
153
|
# :user_id => current_user.id, # => required
|
|
151
154
|
# :user_info => { # => optional - for example
|
|
152
155
|
# :name => current_user.name,
|
|
@@ -171,6 +174,15 @@ module Pusher
|
|
|
171
174
|
r
|
|
172
175
|
end
|
|
173
176
|
|
|
177
|
+
def shared_secret(encryption_master_key)
|
|
178
|
+
return unless encryption_master_key
|
|
179
|
+
|
|
180
|
+
secret_string = @name + encryption_master_key
|
|
181
|
+
digest = OpenSSL::Digest::SHA256.new
|
|
182
|
+
digest << secret_string
|
|
183
|
+
digest.digest
|
|
184
|
+
end
|
|
185
|
+
|
|
174
186
|
private
|
|
175
187
|
|
|
176
188
|
def validate_socket_id(socket_id)
|
data/lib/pusher/client.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
1
3
|
require 'pusher-signature'
|
|
2
4
|
|
|
3
5
|
module Pusher
|
|
4
6
|
class Client
|
|
5
|
-
attr_accessor :scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
|
|
7
|
+
attr_accessor :scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme, :encryption_master_key
|
|
6
8
|
attr_reader :http_proxy, :proxy
|
|
7
9
|
attr_writer :connect_timeout, :send_timeout, :receive_timeout,
|
|
8
10
|
:keep_alive_timeout
|
|
@@ -12,6 +14,11 @@ module Pusher
|
|
|
12
14
|
# Loads the configuration from an url in the environment
|
|
13
15
|
def self.from_env(key = 'PUSHER_URL')
|
|
14
16
|
url = ENV[key] || raise(ConfigurationError, key)
|
|
17
|
+
from_url(url)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Loads the configuration from a url
|
|
21
|
+
def self.from_url(url)
|
|
15
22
|
client = new
|
|
16
23
|
client.url = url
|
|
17
24
|
client
|
|
@@ -22,6 +29,12 @@ module Pusher
|
|
|
22
29
|
:scheme => 'http',
|
|
23
30
|
:port => 80,
|
|
24
31
|
}
|
|
32
|
+
|
|
33
|
+
if options[:use_tls] || options[:encrypted]
|
|
34
|
+
default_options[:scheme] = "https"
|
|
35
|
+
default_options[:port] = 443
|
|
36
|
+
end
|
|
37
|
+
|
|
25
38
|
merged_options = default_options.merge(options)
|
|
26
39
|
|
|
27
40
|
if options.has_key?(:host)
|
|
@@ -44,6 +57,11 @@ module Pusher
|
|
|
44
57
|
:scheme, :host, :port, :app_id, :key, :secret, :notification_host, :notification_scheme
|
|
45
58
|
)
|
|
46
59
|
|
|
60
|
+
if options.has_key?(:encryption_master_key_base64)
|
|
61
|
+
@encryption_master_key =
|
|
62
|
+
Base64.decode64(options[:encryption_master_key_base64])
|
|
63
|
+
end
|
|
64
|
+
|
|
47
65
|
@http_proxy = nil
|
|
48
66
|
self.http_proxy = options[:http_proxy] if options[:http_proxy]
|
|
49
67
|
|
|
@@ -127,7 +145,13 @@ module Pusher
|
|
|
127
145
|
@connect_timeout, @send_timeout, @receive_timeout = value, value, value
|
|
128
146
|
end
|
|
129
147
|
|
|
130
|
-
|
|
148
|
+
# Set an encryption_master_key to use with private-encrypted channels from
|
|
149
|
+
# a base64 encoded string.
|
|
150
|
+
def encryption_master_key_base64=(s)
|
|
151
|
+
@encryption_master_key = s ? Base64.decode64(s) : nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
## INTERACT WITH THE API ##
|
|
131
155
|
|
|
132
156
|
def resource(path)
|
|
133
157
|
Resource.new(self, path)
|
|
@@ -351,7 +375,13 @@ module Pusher
|
|
|
351
375
|
#
|
|
352
376
|
def authenticate(channel_name, socket_id, custom_data = nil)
|
|
353
377
|
channel_instance = channel(channel_name)
|
|
354
|
-
channel_instance.authenticate(socket_id, custom_data)
|
|
378
|
+
r = channel_instance.authenticate(socket_id, custom_data)
|
|
379
|
+
if channel_name.match(/^private-encrypted-/)
|
|
380
|
+
r[:shared_secret] = Base64.strict_encode64(
|
|
381
|
+
channel_instance.shared_secret(encryption_master_key)
|
|
382
|
+
)
|
|
383
|
+
end
|
|
384
|
+
r
|
|
355
385
|
end
|
|
356
386
|
|
|
357
387
|
# @private Construct a net/http http client
|
|
@@ -402,10 +432,17 @@ module Pusher
|
|
|
402
432
|
channels = Array(channels).map(&:to_s)
|
|
403
433
|
raise Pusher::Error, "Too many channels (#{channels.length}), max 10" if channels.length > 10
|
|
404
434
|
|
|
435
|
+
encoded_data = if channels.any?{ |c| c.match(/^private-encrypted-/) } then
|
|
436
|
+
raise Pusher::Error, "Cannot trigger to multiple channels if any are encrypted" if channels.length > 1
|
|
437
|
+
encrypt(channels[0], encode_data(data))
|
|
438
|
+
else
|
|
439
|
+
encode_data(data)
|
|
440
|
+
end
|
|
441
|
+
|
|
405
442
|
params.merge({
|
|
406
443
|
name: event_name,
|
|
407
444
|
channels: channels,
|
|
408
|
-
data:
|
|
445
|
+
data: encoded_data,
|
|
409
446
|
})
|
|
410
447
|
end
|
|
411
448
|
|
|
@@ -413,7 +450,11 @@ module Pusher
|
|
|
413
450
|
{
|
|
414
451
|
batch: events.map do |event|
|
|
415
452
|
event.dup.tap do |e|
|
|
416
|
-
e[:data] =
|
|
453
|
+
e[:data] = if e[:channel].match(/^private-encrypted-/) then
|
|
454
|
+
encrypt(e[:channel], encode_data(e[:data]))
|
|
455
|
+
else
|
|
456
|
+
encode_data(e[:data])
|
|
457
|
+
end
|
|
417
458
|
end
|
|
418
459
|
end
|
|
419
460
|
}
|
|
@@ -425,8 +466,37 @@ module Pusher
|
|
|
425
466
|
MultiJson.encode(data)
|
|
426
467
|
end
|
|
427
468
|
|
|
469
|
+
# Encrypts a message with a key derived from the master key and channel
|
|
470
|
+
# name
|
|
471
|
+
def encrypt(channel_name, encoded_data)
|
|
472
|
+
raise ConfigurationError, :encryption_master_key unless @encryption_master_key
|
|
473
|
+
|
|
474
|
+
# Only now load rbnacl, so that people that aren't using it don't need to
|
|
475
|
+
# install libsodium
|
|
476
|
+
require_rbnacl
|
|
477
|
+
|
|
478
|
+
secret_box = RbNaCl::SecretBox.new(
|
|
479
|
+
channel(channel_name).shared_secret(@encryption_master_key)
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
nonce = RbNaCl::Random.random_bytes(secret_box.nonce_bytes)
|
|
483
|
+
ciphertext = secret_box.encrypt(nonce, encoded_data)
|
|
484
|
+
|
|
485
|
+
MultiJson.encode({
|
|
486
|
+
"nonce" => Base64::encode64(nonce),
|
|
487
|
+
"ciphertext" => Base64::encode64(ciphertext),
|
|
488
|
+
})
|
|
489
|
+
end
|
|
490
|
+
|
|
428
491
|
def configured?
|
|
429
492
|
host && scheme && key && secret && app_id
|
|
430
493
|
end
|
|
494
|
+
|
|
495
|
+
def require_rbnacl
|
|
496
|
+
require 'rbnacl'
|
|
497
|
+
rescue LoadError => e
|
|
498
|
+
$stderr.puts "You don't have rbnacl installed in your application. Please add it to your Gemfile and run bundle install"
|
|
499
|
+
raise e
|
|
500
|
+
end
|
|
431
501
|
end
|
|
432
502
|
end
|
data/lib/pusher/request.rb
CHANGED
|
@@ -94,6 +94,8 @@ module Pusher
|
|
|
94
94
|
raise Error, "404 Not found (#{@uri.path})"
|
|
95
95
|
when 407
|
|
96
96
|
raise Error, "Proxy Authentication Required"
|
|
97
|
+
when 413
|
|
98
|
+
raise Error, "Payload Too Large > 10KB"
|
|
97
99
|
else
|
|
98
100
|
raise Error, "Unknown error (status code #{status_code}): #{body}"
|
|
99
101
|
end
|
data/lib/pusher/version.rb
CHANGED
data/pusher.gemspec
CHANGED
|
@@ -9,22 +9,23 @@ Gem::Specification.new do |s|
|
|
|
9
9
|
s.authors = ["Pusher"]
|
|
10
10
|
s.email = ["support@pusher.com"]
|
|
11
11
|
s.homepage = "http://github.com/pusher/pusher-http-ruby"
|
|
12
|
-
s.summary = %q{Pusher API client}
|
|
13
|
-
s.description = %q{Wrapper for
|
|
12
|
+
s.summary = %q{Pusher Channels API client}
|
|
13
|
+
s.description = %q{Wrapper for Pusher Channels REST api: : https://pusher.com/channels}
|
|
14
14
|
s.license = "MIT"
|
|
15
15
|
|
|
16
|
-
s.add_dependency "multi_json", "~> 1.
|
|
16
|
+
s.add_dependency "multi_json", "~> 1.15"
|
|
17
17
|
s.add_dependency 'pusher-signature', "~> 0.1.8"
|
|
18
|
-
s.add_dependency "httpclient", "~> 2.
|
|
18
|
+
s.add_dependency "httpclient", "~> 2.8"
|
|
19
19
|
s.add_dependency "jruby-openssl" if defined?(JRUBY_VERSION)
|
|
20
20
|
|
|
21
|
-
s.add_development_dependency "rspec", "~> 3.
|
|
22
|
-
s.add_development_dependency "webmock"
|
|
23
|
-
s.add_development_dependency "em-http-request", "~> 1.1
|
|
24
|
-
s.add_development_dependency "addressable", "
|
|
25
|
-
s.add_development_dependency "rake", "~>
|
|
26
|
-
s.add_development_dependency "rack", "~>
|
|
27
|
-
s.add_development_dependency "json", "~>
|
|
21
|
+
s.add_development_dependency "rspec", "~> 3.9"
|
|
22
|
+
s.add_development_dependency "webmock", "~> 3.9"
|
|
23
|
+
s.add_development_dependency "em-http-request", "~> 1.1"
|
|
24
|
+
s.add_development_dependency "addressable", "~> 2.7"
|
|
25
|
+
s.add_development_dependency "rake", "~> 13.0"
|
|
26
|
+
s.add_development_dependency "rack", "~> 2.2"
|
|
27
|
+
s.add_development_dependency "json", "~> 2.3"
|
|
28
|
+
s.add_development_dependency "rbnacl", "~> 7.1"
|
|
28
29
|
|
|
29
30
|
s.files = `git ls-files`.split("\n")
|
|
30
31
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/spec/channel_spec.rb
CHANGED
|
@@ -71,15 +71,17 @@ describe Pusher::Channel do
|
|
|
71
71
|
|
|
72
72
|
describe '#info' do
|
|
73
73
|
it "should call the Client#channel_info" do
|
|
74
|
-
expect(@client).to receive(:get)
|
|
74
|
+
expect(@client).to receive(:get)
|
|
75
|
+
.with("/channels/mychannel", anything)
|
|
76
|
+
.and_return({:occupied => true, :subscription_count => 12})
|
|
75
77
|
@channel = @client['mychannel']
|
|
76
78
|
@channel.info
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
it "should assemble the requested attributes into the info option" do
|
|
80
|
-
expect(@client).to receive(:get)
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
expect(@client).to receive(:get)
|
|
83
|
+
.with(anything, {:info => "user_count,connection_count"})
|
|
84
|
+
.and_return({:occupied => true, :subscription_count => 12, :user_count => 12})
|
|
83
85
|
@channel = @client['presence-foo']
|
|
84
86
|
@channel.info(%w{user_count connection_count})
|
|
85
87
|
end
|
|
@@ -165,4 +167,23 @@ describe Pusher::Channel do
|
|
|
165
167
|
}.to raise_error Pusher::Error
|
|
166
168
|
end
|
|
167
169
|
end
|
|
170
|
+
|
|
171
|
+
describe `#shared_secret` do
|
|
172
|
+
before(:each) do
|
|
173
|
+
@channel.instance_variable_set(:@name, 'private-encrypted-1')
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'should return a shared_secret based on the channel name and encryption master key' do
|
|
177
|
+
key = '3W1pfB/Etr+ZIlfMWwZP3gz8jEeCt4s2pe6Vpr+2c3M='
|
|
178
|
+
shared_secret = @channel.shared_secret(key)
|
|
179
|
+
expect(Base64.strict_encode64(shared_secret)).to eq(
|
|
180
|
+
"6zeEp/chneRPS1cbK/hGeG860UhHomxSN6hTgzwT20I="
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'should return nil if missing encryption master key' do
|
|
185
|
+
shared_secret = @channel.shared_secret(nil)
|
|
186
|
+
expect(shared_secret).to be_nil
|
|
187
|
+
end
|
|
188
|
+
end
|
|
168
189
|
end
|
data/spec/client_spec.rb
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'base64'
|
|
2
2
|
|
|
3
|
+
require 'rbnacl'
|
|
3
4
|
require 'em-http'
|
|
4
5
|
|
|
6
|
+
require 'spec_helper'
|
|
7
|
+
|
|
8
|
+
encryption_master_key = RbNaCl::Random.random_bytes(32)
|
|
9
|
+
|
|
5
10
|
describe Pusher do
|
|
6
11
|
# The behaviour should be the same when using the Client object, or the
|
|
7
12
|
# 'global' client delegated through the Pusher class
|
|
@@ -87,19 +92,53 @@ describe Pusher do
|
|
|
87
92
|
expect(@client.host).to eq('api.staging.pusherapp.com')
|
|
88
93
|
end
|
|
89
94
|
|
|
90
|
-
it 'should
|
|
95
|
+
it 'should override the url configuration if it comes after' do
|
|
91
96
|
@client.url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
|
92
97
|
@client.cluster = 'eu'
|
|
93
98
|
expect(@client.host).to eq('api-eu.pusher.com')
|
|
94
99
|
end
|
|
95
100
|
|
|
96
|
-
it 'should
|
|
101
|
+
it 'should override the host configuration if it comes after' do
|
|
97
102
|
@client.host = 'api.staging.pusher.com'
|
|
98
103
|
@client.cluster = 'eu'
|
|
99
104
|
expect(@client.host).to eq('api-eu.pusher.com')
|
|
100
105
|
end
|
|
101
106
|
end
|
|
102
107
|
|
|
108
|
+
describe 'configuring TLS' do
|
|
109
|
+
it 'should set port and scheme if "use_tls" enabled' do
|
|
110
|
+
client = Pusher::Client.new({
|
|
111
|
+
:use_tls => true,
|
|
112
|
+
})
|
|
113
|
+
expect(client.scheme).to eq('https')
|
|
114
|
+
expect(client.port).to eq(443)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'should set port and scheme if "encrypted" enabled' do
|
|
118
|
+
client = Pusher::Client.new({
|
|
119
|
+
:encrypted => true,
|
|
120
|
+
})
|
|
121
|
+
expect(client.scheme).to eq('https')
|
|
122
|
+
expect(client.port).to eq(443)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'should use non-TLS port and scheme if "encrypted" or "use_tls" are not set' do
|
|
126
|
+
client = Pusher::Client.new
|
|
127
|
+
expect(client.scheme).to eq('http')
|
|
128
|
+
expect(client.port).to eq(80)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'should override port if "use_tls" option set but a different port is specified' do
|
|
132
|
+
client = Pusher::Client.new({
|
|
133
|
+
:use_tls => true,
|
|
134
|
+
:port => 8443
|
|
135
|
+
})
|
|
136
|
+
expect(client.scheme).to eq('https')
|
|
137
|
+
expect(client.port).to eq(8443)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
|
|
103
142
|
describe 'configuring a http proxy' do
|
|
104
143
|
it "should be possible to configure everything by setting the http_proxy" do
|
|
105
144
|
@client.http_proxy = 'http://someuser:somepassword@proxy.host.com:8080'
|
|
@@ -109,6 +148,10 @@ describe Pusher do
|
|
|
109
148
|
end
|
|
110
149
|
|
|
111
150
|
describe 'configuring from env' do
|
|
151
|
+
after do
|
|
152
|
+
ENV['PUSHER_URL'] = nil
|
|
153
|
+
end
|
|
154
|
+
|
|
112
155
|
it "works" do
|
|
113
156
|
url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
|
114
157
|
ENV['PUSHER_URL'] = url
|
|
@@ -118,7 +161,27 @@ describe Pusher do
|
|
|
118
161
|
expect(client.secret).to eq("somesecret")
|
|
119
162
|
expect(client.app_id).to eq("87")
|
|
120
163
|
expect(client.url.to_s).to eq("http://api.staging.pusherapp.com:8080/apps/87")
|
|
121
|
-
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe 'configuring from url' do
|
|
168
|
+
it "works" do
|
|
169
|
+
url = "http://somekey:somesecret@api.staging.pusherapp.com:8080/apps/87"
|
|
170
|
+
|
|
171
|
+
client = Pusher::Client.from_url(url)
|
|
172
|
+
expect(client.key).to eq("somekey")
|
|
173
|
+
expect(client.secret).to eq("somesecret")
|
|
174
|
+
expect(client.app_id).to eq("87")
|
|
175
|
+
expect(client.url.to_s).to eq("http://api.staging.pusherapp.com:8080/apps/87")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe 'can set encryption_master_key_base64' do
|
|
180
|
+
it "sets encryption_master_key" do
|
|
181
|
+
@client.encryption_master_key_base64 =
|
|
182
|
+
Base64.strict_encode64(encryption_master_key)
|
|
183
|
+
|
|
184
|
+
expect(@client.encryption_master_key).to eq(encryption_master_key)
|
|
122
185
|
end
|
|
123
186
|
end
|
|
124
187
|
|
|
@@ -127,6 +190,8 @@ describe Pusher do
|
|
|
127
190
|
@client.app_id = '20'
|
|
128
191
|
@client.key = '12345678900000001'
|
|
129
192
|
@client.secret = '12345678900000001'
|
|
193
|
+
@client.encryption_master_key_base64 =
|
|
194
|
+
Base64.strict_encode64(encryption_master_key)
|
|
130
195
|
end
|
|
131
196
|
|
|
132
197
|
describe '#[]' do
|
|
@@ -211,6 +276,19 @@ describe Pusher do
|
|
|
211
276
|
})
|
|
212
277
|
end
|
|
213
278
|
|
|
279
|
+
it 'should include a shared_secret if the private-encrypted channel' do
|
|
280
|
+
allow(MultiJson).to receive(:encode).with(@custom_data).and_return 'a json string'
|
|
281
|
+
@client.instance_variable_set(:@encryption_master_key, '3W1pfB/Etr+ZIlfMWwZP3gz8jEeCt4s2pe6Vpr+2c3M=')
|
|
282
|
+
|
|
283
|
+
response = @client.authenticate('private-encrypted-test_channel', '1.1', @custom_data)
|
|
284
|
+
|
|
285
|
+
expect(response).to eq({
|
|
286
|
+
:auth => "12345678900000001:#{hmac(@client.secret, "1.1:private-encrypted-test_channel:a json string")}",
|
|
287
|
+
:shared_secret => "o0L3QnIovCeRC8KTD8KBRlmi31dGzHVS2M93uryqDdw=",
|
|
288
|
+
:channel_data => 'a json string'
|
|
289
|
+
})
|
|
290
|
+
end
|
|
291
|
+
|
|
214
292
|
end
|
|
215
293
|
|
|
216
294
|
describe '#trigger' do
|
|
@@ -272,6 +350,46 @@ describe Pusher do
|
|
|
272
350
|
}
|
|
273
351
|
end
|
|
274
352
|
end
|
|
353
|
+
|
|
354
|
+
it "should fail to publish to encrypted channels when missing key" do
|
|
355
|
+
@client.encryption_master_key_base64 = nil
|
|
356
|
+
expect {
|
|
357
|
+
@client.trigger('private-encrypted-channel', 'event', {'some' => 'data'})
|
|
358
|
+
}.to raise_error(Pusher::ConfigurationError)
|
|
359
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it "should fail to publish to multiple channels if one is encrypted" do
|
|
363
|
+
expect {
|
|
364
|
+
@client.trigger(
|
|
365
|
+
['private-encrypted-channel', 'some-other-channel'],
|
|
366
|
+
'event',
|
|
367
|
+
{'some' => 'data'},
|
|
368
|
+
)
|
|
369
|
+
}.to raise_error(Pusher::Error)
|
|
370
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
it "should encrypt publishes to encrypted channels" do
|
|
374
|
+
@client.trigger(
|
|
375
|
+
'private-encrypted-channel',
|
|
376
|
+
'event',
|
|
377
|
+
{'some' => 'data'},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
|
381
|
+
data = MultiJson.decode(MultiJson.decode(req.body)["data"])
|
|
382
|
+
|
|
383
|
+
key = RbNaCl::Hash.sha256(
|
|
384
|
+
'private-encrypted-channel' + encryption_master_key
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
|
388
|
+
Base64.decode64(data["nonce"]),
|
|
389
|
+
Base64.decode64(data["ciphertext"]),
|
|
390
|
+
))).to eq({ 'some' => 'data' })
|
|
391
|
+
}
|
|
392
|
+
end
|
|
275
393
|
end
|
|
276
394
|
|
|
277
395
|
describe '#trigger_batch' do
|
|
@@ -303,6 +421,55 @@ describe Pusher do
|
|
|
303
421
|
)
|
|
304
422
|
}
|
|
305
423
|
end
|
|
424
|
+
|
|
425
|
+
it "should fail to publish to encrypted channels when missing key" do
|
|
426
|
+
@client.encryption_master_key_base64 = nil
|
|
427
|
+
expect {
|
|
428
|
+
@client.trigger_batch(
|
|
429
|
+
{
|
|
430
|
+
channel: 'private-encrypted-channel',
|
|
431
|
+
name: 'event',
|
|
432
|
+
data: {'some' => 'data'},
|
|
433
|
+
},
|
|
434
|
+
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
|
435
|
+
)
|
|
436
|
+
}.to raise_error(Pusher::ConfigurationError)
|
|
437
|
+
expect(WebMock).not_to have_requested(:post, @api_path)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it "should encrypt publishes to encrypted channels" do
|
|
441
|
+
@client.trigger_batch(
|
|
442
|
+
{
|
|
443
|
+
channel: 'private-encrypted-channel',
|
|
444
|
+
name: 'event',
|
|
445
|
+
data: {'some' => 'data'},
|
|
446
|
+
},
|
|
447
|
+
{channel: 'mychannel', name: 'event', data: 'already encoded'},
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
expect(WebMock).to have_requested(:post, @api_path).with { |req|
|
|
451
|
+
batch = MultiJson.decode(req.body)["batch"]
|
|
452
|
+
expect(batch.length).to eq(2)
|
|
453
|
+
|
|
454
|
+
expect(batch[0]["channel"]).to eq("private-encrypted-channel")
|
|
455
|
+
expect(batch[0]["name"]).to eq("event")
|
|
456
|
+
|
|
457
|
+
data = MultiJson.decode(batch[0]["data"])
|
|
458
|
+
|
|
459
|
+
key = RbNaCl::Hash.sha256(
|
|
460
|
+
'private-encrypted-channel' + encryption_master_key
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
expect(MultiJson.decode(RbNaCl::SecretBox.new(key).decrypt(
|
|
464
|
+
Base64.decode64(data["nonce"]),
|
|
465
|
+
Base64.decode64(data["ciphertext"]),
|
|
466
|
+
))).to eq({ 'some' => 'data' })
|
|
467
|
+
|
|
468
|
+
expect(batch[1]["channel"]).to eq("mychannel")
|
|
469
|
+
expect(batch[1]["name"]).to eq("event")
|
|
470
|
+
expect(batch[1]["data"]).to eq("already encoded")
|
|
471
|
+
}
|
|
472
|
+
end
|
|
306
473
|
end
|
|
307
474
|
|
|
308
475
|
describe '#trigger_async' do
|
|
@@ -415,6 +582,11 @@ describe Pusher do
|
|
|
415
582
|
expect { call_api }.to raise_error(Pusher::Error, 'Proxy Authentication Required')
|
|
416
583
|
end
|
|
417
584
|
|
|
585
|
+
it "should raise Pusher::Error if pusher returns 413" do
|
|
586
|
+
stub_request(verb, @url_regexp).to_return({:status => 413})
|
|
587
|
+
expect { call_api }.to raise_error(Pusher::Error, 'Payload Too Large > 10KB')
|
|
588
|
+
end
|
|
589
|
+
|
|
418
590
|
it "should raise Pusher::Error if pusher returns 500" do
|
|
419
591
|
stub_request(verb, @url_regexp).to_return({:status => 500, :body => "some error"})
|
|
420
592
|
expect { call_api }.to raise_error(Pusher::Error, 'Unknown error (status code 500): some error')
|
|
@@ -612,4 +784,3 @@ describe Pusher do
|
|
|
612
784
|
end
|
|
613
785
|
end
|
|
614
786
|
end
|
|
615
|
-
|