slangerq 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'Integration' do
5
+
6
+ before(:each) { start_slanger }
7
+
8
+ context "connecting with invalid credentials" do
9
+ it "sends an error message" do
10
+ messages = em_stream(key: 'bogus_key') do |websocket, messages|
11
+ websocket.callback { EM.stop }
12
+ end
13
+ expect(messages).to have_attributes count: 1, last_event: 'pusher:error',
14
+ connection_established: false, id_present: false
15
+ messages.first['data'] == 'Could not find app by key bogus_key'
16
+ end
17
+ end
18
+
19
+ context "connecting with valid credentials" do
20
+ it "should succeed and include activity_timeout value in handshake" do
21
+ messages = em_stream do |websocket, messages|
22
+ websocket.callback { EM.stop }
23
+ end
24
+ expect(messages).to have_attributes activity_timeout: Slanger::Config.activity_timeout,
25
+ connection_established: true, id_present: true
26
+ end
27
+ end
28
+
29
+ context "connect with valid protocol version" do
30
+ it "should connect successfuly" do
31
+ messages = em_stream do |websocket, messages|
32
+ websocket.callback { EM.stop }
33
+ end
34
+ expect(messages).to have_attributes connection_established: true, id_present: true
35
+ end
36
+ end
37
+
38
+ context "connect with invalid protocol version" do
39
+ it "should not connect successfuly with version bigger than supported" do
40
+ messages = em_stream(protocol: "20") do |websocket, messages|
41
+ websocket.callback { EM.stop }
42
+ end
43
+ expect(messages).to have_attributes connection_established: false, id_present: false,
44
+ last_event: 'pusher:error'
45
+ end
46
+
47
+ it "should not connect successfuly without specified version" do
48
+ messages = em_stream(protocol: nil) do |websocket, messages|
49
+ websocket.callback { EM.stop }
50
+ end
51
+ expect(messages).to have_attributes connection_established: false, id_present: false,
52
+ last_event: 'pusher:error'
53
+ end
54
+ end
55
+
56
+ context "given invalid JSON as input" do
57
+ it 'should not crash' do
58
+ messages = em_stream do |websocket, messages|
59
+ websocket.callback do
60
+ websocket.send("{ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }23123")
61
+ EM.next_tick { EM.stop }
62
+ end
63
+ end
64
+
65
+ EM.run { new_websocket.tap { |u| u.stream { EM.next_tick { EM.stop } } }}
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,157 @@
1
+ #encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Integration' do
6
+
7
+ before(:each) { start_slanger }
8
+
9
+ describe 'presence channels:' do
10
+ context 'subscribing without channel data' do
11
+ context 'and bogus authentication credentials' do
12
+ it 'sends back an error message' do
13
+ messages = em_stream do |websocket, messages|
14
+ case messages.length
15
+ when 1
16
+ websocket.send({ event: 'pusher:subscribe', data: { channel: 'presence-channel', auth: 'bogus' } }.to_json)
17
+ else
18
+ EM.stop
19
+ end
20
+ end
21
+
22
+ expect(messages).to have_attributes connection_established: true, id_present: true,
23
+ count: 2,
24
+ last_event: 'pusher:error'
25
+
26
+ expect(JSON.parse(messages.last['data'])['message']).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'subscribing with channel data' do
32
+ context 'and bogus authentication credentials' do
33
+ it 'sends back an error message' do
34
+ messages = em_stream do |websocket, messages|
35
+ case messages.length
36
+ when 1
37
+ websocket.send({ event: 'pusher:subscribe', data: {
38
+ channel: 'presence-lel',
39
+ auth: 'boog',
40
+ channel_data: {
41
+ user_id: "barry",
42
+ }
43
+ }.to_json }.to_json)
44
+ else
45
+ EM.stop
46
+ end
47
+ end
48
+
49
+ expect(messages).to have_attributes first_event: 'pusher:connection_established', count: 2,
50
+ id_present: true
51
+
52
+ # Channel id should be in the payload
53
+ expect(messages.last['event']).to eq('pusher:error')
54
+ expect(JSON.parse(messages.last['data'])['message']).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
55
+ end
56
+ end
57
+
58
+ context 'with genuine authentication credentials' do
59
+ it 'sends back a success message' do
60
+ messages = em_stream do |websocket, messages|
61
+ case messages.length
62
+ when 1
63
+ send_subscribe( user: websocket,
64
+ user_id: '0f177369a3b71275d25ab1b44db9f95f',
65
+ name: 'SG',
66
+ message: messages.first)
67
+ else
68
+ EM.stop
69
+ end
70
+ end
71
+
72
+ expect(messages).to have_attributes connection_established: true, count: 2
73
+
74
+ expect(messages.last).to eq({"channel"=>"presence-channel",
75
+ "event" =>"pusher_internal:subscription_succeeded",
76
+ "data" => "{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}"})
77
+ end
78
+
79
+
80
+
81
+
82
+ context 'with more than one subscriber subscribed to the channel' do
83
+ it 'sends a member added message to the existing subscribers' do
84
+ messages = em_stream do |user1, messages|
85
+ case messages.length
86
+ when 1
87
+ send_subscribe(user: user1,
88
+ user_id: '0f177369a3b71275d25ab1b44db9f95f',
89
+ name: 'SG',
90
+ message: messages.first)
91
+
92
+ when 2
93
+ new_websocket.tap do |u|
94
+ u.stream do |message|
95
+ message = JSON.parse(message)
96
+ if message['event'] == 'pusher:connection_established'
97
+ send_subscribe \
98
+ user: u, user_id: '37960509766262569d504f02a0ee986d',
99
+ name: 'CHROME', message: message
100
+ end
101
+ end
102
+ end
103
+ else
104
+ EM.stop
105
+ end
106
+
107
+ end
108
+
109
+ expect(messages).to have_attributes connection_established: true, count: 3
110
+ # Channel id should be in the payload
111
+ expect(messages[1]).to eq({"channel"=>"presence-channel", "event"=>"pusher_internal:subscription_succeeded",
112
+ "data"=>"{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}"})
113
+
114
+ expect(messages.last).to eq({"channel"=>"presence-channel", "event"=>"pusher_internal:member_added",
115
+ "data"=>{"user_id"=>"37960509766262569d504f02a0ee986d", "user_info"=>{"name"=>"CHROME"}}})
116
+ end
117
+
118
+ it 'does not send multiple member added and member removed messages if one subscriber opens multiple connections, i.e. multiple browser tabs.' do
119
+ messages = em_stream do |user1, messages|
120
+ case messages.length
121
+ when 1
122
+ send_subscribe(user: user1,
123
+ user_id: '0f177369a3b71275d25ab1b44db9f95f',
124
+ name: 'SG',
125
+ message: messages.first)
126
+
127
+ when 2
128
+ 10.times do
129
+ new_websocket.tap do |u|
130
+ u.stream do |message|
131
+ # remove stream callback
132
+ ## close the connection in the next tick as soon as subscription is acknowledged
133
+ u.stream { EM.next_tick { u.close_connection } }
134
+
135
+ send_subscribe({ user: u,
136
+ user_id: '37960509766262569d504f02a0ee986d',
137
+ name: 'CHROME',
138
+ message: JSON.parse(message)})
139
+ end
140
+ end
141
+ end
142
+ when 4
143
+ EM.next_tick { EM.stop }
144
+ end
145
+
146
+ end
147
+
148
+ # There should only be one set of presence messages sent to the refernce user for the second user.
149
+ expect(messages.one? { |message| message['event'] == 'pusher_internal:member_added' && message['data']['user_id'] == '37960509766262569d504f02a0ee986d' }).to eq(true)
150
+ expect(messages.one? { |message| message['event'] == 'pusher_internal:member_removed' && message['data']['user_id'] == '37960509766262569d504f02a0ee986d' }).to eq(true)
151
+
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,79 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'Integration' do
5
+
6
+ before(:each) { start_slanger }
7
+
8
+ describe 'private channels' do
9
+ context 'with valid authentication credentials:' do
10
+ it 'accepts the subscription request' do
11
+ messages = em_stream do |websocket, messages|
12
+ case messages.length
13
+ when 1
14
+ private_channel websocket, messages.first
15
+ else
16
+ EM.stop
17
+ end
18
+ end
19
+
20
+ expect(messages).to have_attributes connection_established: true,
21
+ count: 2,
22
+ id_present: true,
23
+ last_event: 'pusher_internal:subscription_succeeded'
24
+ end
25
+ end
26
+
27
+ context 'with bogus authentication credentials:' do
28
+ it 'sends back an error message' do
29
+ messages = em_stream do |websocket, messages|
30
+ case messages.length
31
+ when 1
32
+ websocket.send({ event: 'pusher:subscribe',
33
+ data: { channel: 'private-channel',
34
+ auth: 'bogus' } }.to_json)
35
+ else
36
+ EM.stop
37
+ end
38
+ end
39
+
40
+ expect(messages).to have_attributes connection_established: true, count: 2, id_present: true, last_event:
41
+ 'pusher:error'
42
+
43
+ expect(JSON.parse(messages.last['data'])['message']).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
44
+ end
45
+ end
46
+
47
+ describe 'client events' do
48
+ it "sends event to other channel subscribers" do
49
+ client1_messages, client2_messages = [], []
50
+
51
+ em_thread do
52
+ client1, client2 = new_websocket, new_websocket
53
+ client2_messages, client1_messages = [], []
54
+
55
+ stream(client1, client1_messages) do |message|
56
+ case client1_messages.length
57
+ when 1
58
+ private_channel client1, client1_messages.first
59
+ when 3
60
+ EM.next_tick { EM.stop }
61
+ end
62
+ end
63
+
64
+ stream(client2, client2_messages) do |message|
65
+ case client2_messages.length
66
+ when 1
67
+ private_channel client2, client2_messages.first
68
+ when 2
69
+ client2.send({ event: 'client-something', data: { some: 'stuff' }, channel: 'private-channel' }.to_json)
70
+ end
71
+ end
72
+ end
73
+
74
+ expect(client1_messages.one? { |m| m['event'] == 'client-something' }).to eq(true)
75
+ expect(client2_messages.none? { |m| m['event'] == 'client-something' }).to eq(true)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'lib/slanger/handler.rb'
3
+
4
+ class ReplacedHandler < Slanger::Handler
5
+ def authenticate
6
+ super
7
+ send_payload nil, 'pusher:info', { message: "Welcome!" }
8
+ end
9
+ end
10
+
11
+ describe 'Replacable handler' do
12
+ it 'says welcome' do
13
+ start_slanger_with_options socket_handler: ReplacedHandler
14
+
15
+ msgs = em_stream do |websocket, messages|
16
+ if messages.length == 2
17
+ EM.stop
18
+ end
19
+ end
20
+
21
+ expect(msgs.last).to eq({ "event" => "pusher:info", "data" =>"{\"message\":\"Welcome!\"}" })
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Integration' do
4
+ describe 'Slanger when configured to use SSL' do
5
+ it 'encrypts the connection' do
6
+ start_slanger_with_options tls_options: {
7
+ cert_chain_file: 'spec/server.crt',
8
+ private_key_file: 'spec/server.key'
9
+ }
10
+
11
+ socket = TCPSocket.new('0.0.0.0', 8080)
12
+ expected_cert = OpenSSL::X509::Certificate.new(File.open('spec/server.crt'))
13
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
14
+ ssl_socket.connect
15
+ expect(ssl_socket.peer_cert.to_s).to eq(expected_cert.to_s)
16
+ end
17
+ end
18
+ end
data/spec/server.crt ADDED
@@ -0,0 +1,12 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIBvzCCASgCCQCsMkmDVYNDETANBgkqhkiG9w0BAQUFADAkMQswCQYDVQQGEwJE
3
+ RTEVMBMGA1UEAwwMc2xhbmdlci50ZXN0MB4XDTEyMDQxMTE2NDMwNloXDTE5MDIx
4
+ NDE2NDMwNlowJDELMAkGA1UEBhMCREUxFTATBgNVBAMMDHNsYW5nZXIudGVzdDCB
5
+ nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAmlTxrcqXw+hbdjnpNENgx4p6T+x7
6
+ SgN/5ti3+gr5ZJElebEdJdGymM/KK817GFhLuYSEv72oEVitC1ISCfo/iOu4S71Y
7
+ sGsrdyPVl3cDswSkvmo27J3rtAbY1fNDs68YFAQGH8wlQtPSPvd9KBKg0klafsDU
8
+ VvDYjQ+XZ2+ZKZECAwEAATANBgkqhkiG9w0BAQUFAAOBgQCHGqUddcsTfvV0Nk3F
9
+ zZ5kGvAiZ02MUourZ4GVs5uBYtkIrQ7HAlQbHAbC8d7e0UVgcTwUKgwpw/RfNR/O
10
+ Ho/zn7lPciLQ7VMnOZ2+MfbJ2HIFgZL6qH1gTcQpBW4s3gKR5hFpaGJ+8l/cmEWj
11
+ AvywaOcSLex+q0OwZaiusiDorg==
12
+ -----END CERTIFICATE-----
data/spec/server.key ADDED
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXQIBAAKBgQCaVPGtypfD6Ft2Oek0Q2DHinpP7HtKA3/m2Lf6CvlkkSV5sR0l
3
+ 0bKYz8orzXsYWEu5hIS/vagRWK0LUhIJ+j+I67hLvViwayt3I9WXdwOzBKS+ajbs
4
+ neu0BtjV80OzrxgUBAYfzCVC09I+930oEqDSSVp+wNRW8NiND5dnb5kpkQIDAQAB
5
+ AoGABGzBDSGM3mIQFUCtzgDMiowO27HFCyc0iJLYG4QrCFYdA/MvCcGMZFM40a6v
6
+ g9AsQ6JoB/NRGUY4l+V/fOe+4Iuycf8+vN1mrSVR1lTjy/mOwj900pc4ff6cDv6S
7
+ bI/hm4BNiuj8OD11R+ZK07Lo1iCzBkAy53RkTFcBk74MYgECQQDMyAMT0DhlRBqD
8
+ 4vrPF+GZ+rMYpeTjuNDOZphIwzxpv70uyh2RNg+7F6U91Qxz6vpbIkz8Zf4TgdwM
9
+ u/rroktxAkEAwO6wyzidm2yrPMPtnwxDIYnH/ETdNraa3JyHsjXsQwGAIG80+hCv
10
+ QfCA/LmvNOm/Mpe1EyiAeI1/YJp4a2xwIQJBAKysFpQ1ZehVtbnxwaSwMWXiE/Q7
11
+ pjYyl7cCoXPxVFai+8WhXa8dE8Shmo75v2dbAsGnuZy177jJLiB6vYjFL7ECQQCI
12
+ Zri7lLVo8zVFasgO0F6N0ZmAMzeqvQNTwZ72UcVNwjvRso3j1fPyTJUFGEpUwIWa
13
+ wUMV3mal1HQf2lYUrL/BAkBBtXqOLFHINHUmffdRSV/2HECYXHazb6lAnL8nnQX0
14
+ vin2ujCli9mcYWnrY7zwlXdAxgQv5Q2ByQT9Fd8S7FjA
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,109 @@
1
+ module SlangerHelperMethods
2
+ def start_slanger_with_options options={}
3
+ # Fork service. Our integration tests MUST block the main thread because we want to wait for i/o to finish.
4
+ @server_pid = EM.fork_reactor do
5
+ Thin::Logging.silent = true
6
+
7
+ opts = { host: '0.0.0.0',
8
+ api_port: '4567',
9
+ websocket_port: '8080',
10
+ app_key: '765ec374ae0a69f4ce44',
11
+ secret: 'your-pusher-secret',
12
+ activity_timeout: 100
13
+ }
14
+
15
+ Slanger::Config.load opts.merge(options)
16
+
17
+ Slanger::Service.run
18
+ end
19
+ wait_for_slanger
20
+ end
21
+
22
+ alias start_slanger start_slanger_with_options
23
+
24
+ def stop_slanger
25
+ # Ensure Slanger is properly stopped. No orphaned processes allowed!
26
+ Process.kill 'SIGKILL', @server_pid
27
+ Process.wait @server_pid
28
+ end
29
+
30
+ def wait_for_slanger opts = {}
31
+ opts = { port: 8080 }.update opts
32
+ begin
33
+ TCPSocket.new('0.0.0.0', opts[:port]).close
34
+ rescue
35
+ sleep 0.005
36
+ retry
37
+ end
38
+ end
39
+
40
+ def new_websocket opts = {}
41
+ opts = { key: Pusher.key, protocol: 7 }.update opts
42
+ uri = "ws://0.0.0.0:8080/app/#{opts[:key]}?client=js&version=1.8.5&protocol=#{opts[:protocol]}"
43
+
44
+ EM::HttpRequest.new(uri).get.tap { |ws| ws.errback &errback }
45
+ end
46
+
47
+ def em_stream opts = {}
48
+ messages = []
49
+
50
+ em_thread do
51
+ websocket = new_websocket opts
52
+
53
+ stream(websocket, messages) do |message|
54
+ yield websocket, messages
55
+ end
56
+ end
57
+
58
+ return messages
59
+ end
60
+
61
+ def em_thread
62
+ Thread.new do
63
+ EM.run do
64
+ yield
65
+ end
66
+ end.join
67
+ end
68
+
69
+ def stream websocket, messages
70
+ websocket.stream do |message|
71
+ messages << JSON.parse(message)
72
+
73
+ yield message
74
+ end
75
+ end
76
+
77
+ def send_subscribe options
78
+ info = { user_id: options[:user_id], user_info: { name: options[:name] } }
79
+ socket_id = JSON.parse(options[:message]['data'])['socket_id']
80
+ to_sign = [socket_id, 'presence-channel', info.to_json].join ':'
81
+
82
+ digest = OpenSSL::Digest::SHA256.new
83
+
84
+ options[:user].send({
85
+ event: 'pusher:subscribe',
86
+ data: {
87
+ auth: [Pusher.key, OpenSSL::HMAC.hexdigest(digest, Pusher.secret, to_sign)].join(':'),
88
+ channel_data: info.to_json,
89
+ channel: 'presence-channel'
90
+ }
91
+ }.to_json)
92
+ end
93
+
94
+ def private_channel websocket, message
95
+ socket_id = JSON.parse(message['data'])['socket_id']
96
+ to_sign = [socket_id, 'private-channel'].join ':'
97
+
98
+ digest = OpenSSL::Digest::SHA256.new
99
+
100
+ websocket.send({
101
+ event: 'pusher:subscribe',
102
+ data: {
103
+ auth: [Pusher.key, OpenSSL::HMAC.hexdigest(digest, Pusher.secret, to_sign)].join(':'),
104
+ channel: 'private-channel'
105
+ }
106
+ }.to_json)
107
+
108
+ end
109
+ end