ably 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +9 -0
- data/LICENSE.txt +1 -1
- data/README.md +8 -1
- data/Rakefile +10 -0
- data/ably.gemspec +18 -18
- data/lib/ably.rb +6 -5
- data/lib/ably/auth.rb +11 -14
- data/lib/ably/exceptions.rb +18 -15
- data/lib/ably/logger.rb +102 -0
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +19 -5
- data/lib/ably/models/message_encoders/base.rb +107 -0
- data/lib/ably/models/message_encoders/base64.rb +39 -0
- data/lib/ably/models/message_encoders/cipher.rb +80 -0
- data/lib/ably/models/message_encoders/json.rb +33 -0
- data/lib/ably/models/message_encoders/utf8.rb +33 -0
- data/lib/ably/models/paginated_resource.rb +23 -6
- data/lib/ably/models/presence_message.rb +19 -7
- data/lib/ably/models/protocol_message.rb +5 -4
- data/lib/ably/models/token.rb +2 -2
- data/lib/ably/modules/channels_collection.rb +0 -3
- data/lib/ably/modules/conversions.rb +3 -3
- data/lib/ably/modules/encodeable.rb +68 -0
- data/lib/ably/modules/event_emitter.rb +10 -4
- data/lib/ably/modules/event_machine_helpers.rb +6 -4
- data/lib/ably/modules/http_helpers.rb +7 -2
- data/lib/ably/modules/model_common.rb +2 -0
- data/lib/ably/modules/state_emitter.rb +10 -1
- data/lib/ably/realtime.rb +19 -12
- data/lib/ably/realtime/channel.rb +26 -13
- data/lib/ably/realtime/client.rb +31 -7
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
- data/lib/ably/realtime/connection.rb +152 -46
- data/lib/ably/realtime/connection/connection_manager.rb +168 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
- data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
- data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
- data/lib/ably/realtime/presence.rb +38 -13
- data/lib/ably/rest.rb +7 -5
- data/lib/ably/rest/channel.rb +24 -3
- data/lib/ably/rest/client.rb +56 -17
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +3 -2
- data/lib/ably/rest/middleware/logger.rb +37 -0
- data/lib/ably/rest/presence.rb +10 -2
- data/lib/ably/util/crypto.rb +57 -29
- data/lib/ably/util/pub_sub.rb +11 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_spec.rb +65 -7
- data/spec/acceptance/realtime/connection_spec.rb +123 -27
- data/spec/acceptance/realtime/message_spec.rb +319 -34
- data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
- data/spec/acceptance/realtime/presence_spec.rb +160 -18
- data/spec/acceptance/rest/auth_spec.rb +93 -49
- data/spec/acceptance/rest/base_spec.rb +10 -10
- data/spec/acceptance/rest/channel_spec.rb +35 -19
- data/spec/acceptance/rest/channels_spec.rb +8 -8
- data/spec/acceptance/rest/message_spec.rb +224 -0
- data/spec/acceptance/rest/presence_spec.rb +159 -23
- data/spec/acceptance/rest/stats_spec.rb +5 -5
- data/spec/acceptance/rest/time_spec.rb +4 -4
- data/spec/integration/rest/auth.rb +1 -1
- data/spec/resources/crypto-data-128.json +56 -0
- data/spec/resources/crypto-data-256.json +56 -0
- data/spec/rspec_config.rb +39 -0
- data/spec/spec_helper.rb +4 -42
- data/spec/support/api_helper.rb +1 -1
- data/spec/support/event_machine_helper.rb +0 -5
- data/spec/support/protocol_msgbus_helper.rb +3 -3
- data/spec/support/test_app.rb +3 -3
- data/spec/unit/logger_spec.rb +135 -0
- data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
- data/spec/unit/models/message_spec.rb +16 -1
- data/spec/unit/models/paginated_resource_spec.rb +46 -0
- data/spec/unit/models/presence_message_spec.rb +18 -5
- data/spec/unit/models/token_spec.rb +1 -1
- data/spec/unit/modules/event_emitter_spec.rb +24 -10
- data/spec/unit/realtime/channel_spec.rb +3 -3
- data/spec/unit/realtime/channels_spec.rb +1 -1
- data/spec/unit/realtime/client_spec.rb +44 -2
- data/spec/unit/realtime/connection_spec.rb +2 -2
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
- data/spec/unit/realtime/presence_spec.rb +1 -1
- data/spec/unit/realtime/realtime_spec.rb +3 -3
- data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
- data/spec/unit/rest/channels_spec.rb +1 -1
- data/spec/unit/rest/client_spec.rb +45 -10
- data/spec/unit/util/crypto_spec.rb +82 -0
- data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
- metadata +43 -12
- data/spec/acceptance/crypto.rb +0 -63
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe 'Ably::Realtime::Presence Messages' do
|
5
|
+
include RSpec::EventMachine
|
6
|
+
|
7
|
+
[:msgpack, :json].each do |protocol|
|
8
|
+
context "over #{protocol}" do
|
9
|
+
let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
10
|
+
|
11
|
+
let(:channel_name) { "persisted:#{SecureRandom.hex(2)}" }
|
12
|
+
|
13
|
+
let(:client_one) { Ably::Realtime::Client.new(default_options.merge(client_id: SecureRandom.hex(4))) }
|
14
|
+
let(:channel_client_one) { client_one.channel(channel_name) }
|
15
|
+
let(:presence_client_one) { channel_client_one.presence }
|
16
|
+
|
17
|
+
let(:client_two) { Ably::Realtime::Client.new(default_options.merge(client_id: SecureRandom.hex(4))) }
|
18
|
+
let(:channel_client_two) { client_two.channel(channel_name) }
|
19
|
+
let(:presence_client_two) { channel_client_two.presence }
|
20
|
+
|
21
|
+
let(:data) { SecureRandom.hex(8) }
|
22
|
+
|
23
|
+
it 'provides up to the moment presence history' do
|
24
|
+
run_reactor do
|
25
|
+
presence_client_one.enter(data: data) do
|
26
|
+
presence_client_one.leave do
|
27
|
+
history = presence_client_one.history
|
28
|
+
expect(history.count).to eql(2)
|
29
|
+
|
30
|
+
expect(history[1].action).to eq(:enter)
|
31
|
+
expect(history[1].client_id).to eq(client_one.client_id)
|
32
|
+
|
33
|
+
expect(history[0].action).to eq(:leave)
|
34
|
+
expect(history[0].client_id).to eq(client_one.client_id)
|
35
|
+
expect(history[0].data).to eql(data)
|
36
|
+
|
37
|
+
stop_reactor
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'ensures REST presence history message IDs match ProtocolMessage wrapped message IDs via Realtime' do
|
44
|
+
run_reactor do
|
45
|
+
presence_client_one.subscribe(:enter) do |message|
|
46
|
+
history = presence_client_one.history
|
47
|
+
expect(history.count).to eql(1)
|
48
|
+
|
49
|
+
expect(history[0].id).to eql(message.id)
|
50
|
+
stop_reactor
|
51
|
+
end
|
52
|
+
|
53
|
+
presence_client_one.enter(data: data)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -22,7 +22,7 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
22
22
|
let(:channel_client_two) { client_two.channel(channel_name) }
|
23
23
|
let(:presence_client_two) { channel_client_two.presence }
|
24
24
|
|
25
|
-
let(:
|
25
|
+
let(:data_payload) { SecureRandom.hex(8) }
|
26
26
|
|
27
27
|
specify 'an attached channel that is not presence maintains presence state' do
|
28
28
|
run_reactor do
|
@@ -126,7 +126,7 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
126
126
|
|
127
127
|
specify 'verify two clients appear in members from #get' do
|
128
128
|
run_reactor do
|
129
|
-
presence_client_one.enter(
|
129
|
+
presence_client_one.enter(data: data_payload)
|
130
130
|
presence_client_two.enter
|
131
131
|
|
132
132
|
entered_callback = Proc.new do
|
@@ -140,7 +140,7 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
140
140
|
member_client_two = members.find { |presence| presence.client_id == client_two.client_id }
|
141
141
|
|
142
142
|
expect(member_client_one).to be_a(Ably::Models::PresenceMessage)
|
143
|
-
expect(member_client_one.
|
143
|
+
expect(member_client_one.data).to eql(data_payload)
|
144
144
|
expect(member_client_two).to be_a(Ably::Models::PresenceMessage)
|
145
145
|
|
146
146
|
stop_reactor
|
@@ -158,7 +158,7 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
158
158
|
|
159
159
|
subscribe_client_one_leaving_callback = Proc.new do |presence_message|
|
160
160
|
expect(presence_message.client_id).to eql(client_one.client_id)
|
161
|
-
expect(presence_message.
|
161
|
+
expect(presence_message.data).to eql(data_payload)
|
162
162
|
expect(presence_message.action).to eq(:leave)
|
163
163
|
|
164
164
|
stop_reactor
|
@@ -171,7 +171,7 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
171
171
|
presence_client_two.unsubscribe &subscribe_self_callback
|
172
172
|
presence_client_two.subscribe &subscribe_client_one_leaving_callback
|
173
173
|
|
174
|
-
presence_client_one.leave
|
174
|
+
presence_client_one.leave data: data_payload
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
@@ -182,24 +182,24 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
-
specify '
|
185
|
+
specify 'REST #get returns current members' do
|
186
186
|
run_reactor do
|
187
|
-
presence_client_one.enter(
|
187
|
+
presence_client_one.enter(data: data_payload) do
|
188
188
|
members = channel_rest_client_one.presence.get
|
189
189
|
this_member = members.first
|
190
190
|
|
191
191
|
expect(this_member).to be_a(Ably::Models::PresenceMessage)
|
192
192
|
expect(this_member.client_id).to eql(client_one.client_id)
|
193
|
-
expect(this_member.
|
193
|
+
expect(this_member.data).to eql(data_payload)
|
194
194
|
|
195
195
|
stop_reactor
|
196
196
|
end
|
197
197
|
end
|
198
198
|
end
|
199
199
|
|
200
|
-
specify '
|
200
|
+
specify 'REST #get returns no members once left' do
|
201
201
|
run_reactor do
|
202
|
-
presence_client_one.enter(
|
202
|
+
presence_client_one.enter(data: data_payload) do
|
203
203
|
presence_client_one.leave do
|
204
204
|
members = channel_rest_client_one.presence.get
|
205
205
|
expect(members.count).to eql(0)
|
@@ -209,6 +209,148 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
|
+
context 'encoding and decoding of presence message data' do
|
213
|
+
let(:secret_key) { SecureRandom.hex(32) }
|
214
|
+
let(:cipher_options) { { key: secret_key, algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
215
|
+
let(:channel_name) { SecureRandom.hex(32) }
|
216
|
+
let(:encrypted_channel) { client_one.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
217
|
+
let(:channel_rest_client_one) { client_one.rest_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
218
|
+
|
219
|
+
let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
|
220
|
+
|
221
|
+
let(:data) { { 'key' => SecureRandom.hex(64) } }
|
222
|
+
let(:data_as_json) { data.to_json }
|
223
|
+
let(:data_as_cipher) { crypto.encrypt(data.to_json) }
|
224
|
+
|
225
|
+
it 'encrypts presence message data' do
|
226
|
+
run_reactor do
|
227
|
+
encrypted_channel.attach do
|
228
|
+
encrypted_channel.presence.enter data: data
|
229
|
+
end
|
230
|
+
|
231
|
+
encrypted_channel.presence.__incoming_msgbus__.unsubscribe(:presence) # remove all subscribe callbacks that could decrypt the message
|
232
|
+
encrypted_channel.presence.__incoming_msgbus__.subscribe(:presence) do |presence|
|
233
|
+
if protocol == :json
|
234
|
+
expect(presence['encoding']).to eql('json/utf-8/cipher+aes-256-cbc/base64')
|
235
|
+
expect(crypto.decrypt(Base64.decode64(presence['data']))).to eql(data_as_json)
|
236
|
+
else
|
237
|
+
expect(presence['encoding']).to eql('json/utf-8/cipher+aes-256-cbc')
|
238
|
+
expect(crypto.decrypt(presence['data'])).to eql(data_as_json)
|
239
|
+
end
|
240
|
+
stop_reactor
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
it '#subscribe emits decrypted enter events' do
|
246
|
+
run_reactor do
|
247
|
+
encrypted_channel.attach do
|
248
|
+
encrypted_channel.presence.enter data: data
|
249
|
+
end
|
250
|
+
|
251
|
+
encrypted_channel.presence.subscribe(:enter) do |presence_message|
|
252
|
+
expect(presence_message.encoding).to be_nil
|
253
|
+
expect(presence_message.data).to eql(data)
|
254
|
+
stop_reactor
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
it '#subscribe emits decrypted update events' do
|
260
|
+
run_reactor do
|
261
|
+
encrypted_channel.attach do
|
262
|
+
encrypted_channel.presence.enter(data: 'to be updated') do
|
263
|
+
encrypted_channel.presence.update data: data
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
encrypted_channel.presence.subscribe(:update) do |presence_message|
|
268
|
+
expect(presence_message.encoding).to be_nil
|
269
|
+
expect(presence_message.data).to eql(data)
|
270
|
+
stop_reactor
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it '#subscribe emits decrypted leave events' do
|
276
|
+
run_reactor do
|
277
|
+
encrypted_channel.attach do
|
278
|
+
encrypted_channel.presence.enter(data: 'to be updated') do
|
279
|
+
encrypted_channel.presence.leave data: data
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
encrypted_channel.presence.subscribe(:leave) do |presence_message|
|
284
|
+
expect(presence_message.encoding).to be_nil
|
285
|
+
expect(presence_message.data).to eql(data)
|
286
|
+
stop_reactor
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it '#get returns a list of members with decrypted data' do
|
292
|
+
run_reactor do
|
293
|
+
encrypted_channel.attach do
|
294
|
+
encrypted_channel.presence.enter(data: data) do
|
295
|
+
member = encrypted_channel.presence.get.first
|
296
|
+
expect(member.encoding).to be_nil
|
297
|
+
expect(member.data).to eql(data)
|
298
|
+
stop_reactor
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'REST #get returns a list of members with decrypted data' do
|
305
|
+
run_reactor do
|
306
|
+
encrypted_channel.attach do
|
307
|
+
encrypted_channel.presence.enter(data: data) do
|
308
|
+
member = channel_rest_client_one.presence.get.first
|
309
|
+
expect(member.encoding).to be_nil
|
310
|
+
expect(member.data).to eql(data)
|
311
|
+
stop_reactor
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'when cipher settings do not match publisher' do
|
318
|
+
let(:incompatible_cipher_options) { { key: secret_key, algorithm: 'aes', mode: 'cbc', key_length: 128 } }
|
319
|
+
let(:incompatible_encrypted_channel) { client_two.channel(channel_name, encrypted: true, cipher_params: incompatible_cipher_options) }
|
320
|
+
|
321
|
+
it 'delivers an unencoded presence message left with encoding value' do
|
322
|
+
run_reactor do
|
323
|
+
incompatible_encrypted_channel.attach do
|
324
|
+
encrypted_channel.attach do
|
325
|
+
encrypted_channel.presence.enter(data: data) do
|
326
|
+
member = incompatible_encrypted_channel.presence.get.first
|
327
|
+
expect(member.encoding).to match(/cipher\+aes-256-cbc/)
|
328
|
+
expect(member.data).to_not eql(data)
|
329
|
+
stop_reactor
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'emits an error when cipher does not match and presence data cannot be decoded' do
|
337
|
+
run_reactor do
|
338
|
+
incompatible_encrypted_channel.attach do
|
339
|
+
incompatible_encrypted_channel.on(:error) do |error|
|
340
|
+
expect(error).to be_a(Ably::Exceptions::CipherError)
|
341
|
+
expect(error.message).to match(/Cipher algorithm AES-128-CBC does not match/)
|
342
|
+
stop_reactor
|
343
|
+
end
|
344
|
+
|
345
|
+
encrypted_channel.attach do
|
346
|
+
encrypted_channel.presence.enter data: data
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
212
354
|
specify 'expect :left event once underlying connection is closed' do
|
213
355
|
run_reactor do
|
214
356
|
presence_client_one.on(:left) do
|
@@ -221,14 +363,14 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
221
363
|
end
|
222
364
|
end
|
223
365
|
|
224
|
-
specify 'expect :left event with no client data to retain original
|
366
|
+
specify 'expect :left event with no client data to retain original data in Leave event' do
|
225
367
|
run_reactor do
|
226
368
|
presence_client_one.subscribe(:leave) do |message|
|
227
369
|
expect(presence_client_one.get.count).to eq(0)
|
228
|
-
expect(message.
|
370
|
+
expect(message.data).to eq(data_payload)
|
229
371
|
stop_reactor
|
230
372
|
end
|
231
|
-
presence_client_one.enter(
|
373
|
+
presence_client_one.enter(data: data_payload) do
|
232
374
|
presence_client_one.leave
|
233
375
|
end
|
234
376
|
end
|
@@ -236,20 +378,20 @@ describe 'Ably::Realtime::Presence Messages' do
|
|
236
378
|
|
237
379
|
specify '#update automatically connects' do
|
238
380
|
run_reactor do
|
239
|
-
presence_client_one.update(
|
381
|
+
presence_client_one.update(data: data_payload) do
|
240
382
|
expect(presence_client_one.state).to eq(:entered)
|
241
383
|
stop_reactor
|
242
384
|
end
|
243
385
|
end
|
244
386
|
end
|
245
387
|
|
246
|
-
specify '#update changes the
|
388
|
+
specify '#update changes the data' do
|
247
389
|
run_reactor do
|
248
|
-
presence_client_one.enter(
|
249
|
-
presence_client_one.update(
|
390
|
+
presence_client_one.enter(data: 'prior') do
|
391
|
+
presence_client_one.update(data: data_payload)
|
250
392
|
end
|
251
393
|
presence_client_one.subscribe(:update) do |message|
|
252
|
-
expect(message.
|
394
|
+
expect(message.data).to eql(data_payload)
|
253
395
|
stop_reactor
|
254
396
|
end
|
255
397
|
end
|
@@ -1,19 +1,45 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe Ably::Auth do
|
5
|
+
include Ably::Modules::Conversions
|
3
6
|
|
4
|
-
describe "REST" do
|
5
7
|
[:msgpack, :json].each do |protocol|
|
6
8
|
context "over #{protocol}" do
|
7
9
|
let(:client) do
|
8
10
|
Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
9
11
|
end
|
10
12
|
let(:auth) { client.auth }
|
13
|
+
let(:content_type) do
|
14
|
+
if protocol == :msgpack
|
15
|
+
'application/x-msgpack'
|
16
|
+
else
|
17
|
+
'application/json'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_body_includes(request, protocol, key, val)
|
22
|
+
body = if protocol == :msgpack
|
23
|
+
MessagePack.unpack(request.body)
|
24
|
+
else
|
25
|
+
JSON.parse(request.body)
|
26
|
+
end
|
27
|
+
body[key.to_s].to_s == val.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def serialize(object, protocol)
|
31
|
+
if protocol == :msgpack
|
32
|
+
MessagePack.pack(token_response)
|
33
|
+
else
|
34
|
+
JSON.dump(token_response)
|
35
|
+
end
|
36
|
+
end
|
11
37
|
|
12
38
|
describe "#request_token" do
|
13
39
|
let(:ttl) { 30 * 60 }
|
14
|
-
let(:capability) { { :foo => [
|
40
|
+
let(:capability) { { :foo => ['publish'] } }
|
15
41
|
|
16
|
-
it
|
42
|
+
it 'returns the requested token' do
|
17
43
|
actual_token = auth.request_token(
|
18
44
|
ttl: ttl,
|
19
45
|
capability: capability
|
@@ -25,16 +51,21 @@ describe "REST" do
|
|
25
51
|
expect(actual_token.expires_at).to be_within(2).of(Time.now + ttl)
|
26
52
|
end
|
27
53
|
|
28
|
-
%w(client_id
|
54
|
+
%w(client_id capability nonce timestamp ttl).each do |option|
|
29
55
|
context "option :#{option}", webmock: true do
|
30
56
|
let(:random) { SecureRandom.random_number(1_000_000_000).to_s }
|
31
57
|
let(:options) { { option.to_sym => random } }
|
32
58
|
|
33
|
-
let(:token_response) { { access_token: {} }
|
59
|
+
let(:token_response) { { access_token: {} } }
|
34
60
|
let!(:request_token_stub) do
|
35
61
|
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
36
|
-
with
|
37
|
-
|
62
|
+
with do |request|
|
63
|
+
request_body_includes(request, protocol, option, random)
|
64
|
+
end.to_return(
|
65
|
+
:status => 201,
|
66
|
+
:body => serialize(token_response, protocol),
|
67
|
+
:headers => { 'Content-Type' => content_type }
|
68
|
+
)
|
38
69
|
end
|
39
70
|
|
40
71
|
before { auth.request_token options }
|
@@ -55,11 +86,15 @@ describe "REST" do
|
|
55
86
|
hmac_for(token_request, key_secret)
|
56
87
|
end
|
57
88
|
|
58
|
-
let(:token_response) { { access_token: {} }
|
89
|
+
let(:token_response) { { access_token: {} } }
|
59
90
|
let!(:request_token_stub) do
|
60
91
|
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
61
|
-
with
|
62
|
-
|
92
|
+
with do |request|
|
93
|
+
request_body_includes(request, protocol, 'mac', mac)
|
94
|
+
end.to_return(
|
95
|
+
:status => 201,
|
96
|
+
:body => serialize(token_response, protocol),
|
97
|
+
:headers => { 'Content-Type' => content_type })
|
63
98
|
end
|
64
99
|
|
65
100
|
let!(:token) { auth.request_token(token_options) }
|
@@ -69,7 +104,7 @@ describe "REST" do
|
|
69
104
|
end
|
70
105
|
end
|
71
106
|
|
72
|
-
context
|
107
|
+
context 'with :query_time option' do
|
73
108
|
let(:options) { { query_time: true } }
|
74
109
|
|
75
110
|
it 'queries the server for the time' do
|
@@ -78,7 +113,7 @@ describe "REST" do
|
|
78
113
|
end
|
79
114
|
end
|
80
115
|
|
81
|
-
context
|
116
|
+
context 'without :query_time option' do
|
82
117
|
let(:options) { { query_time: false } }
|
83
118
|
|
84
119
|
it 'queries the server for the time' do
|
@@ -89,8 +124,8 @@ describe "REST" do
|
|
89
124
|
|
90
125
|
context 'with :auth_url option', webmock: true do
|
91
126
|
let(:auth_url) { 'https://www.fictitious.com/get_token' }
|
92
|
-
let(:token_request) { { id: key_id }
|
93
|
-
let(:token_response) { { access_token: { } }
|
127
|
+
let(:token_request) { { id: key_id } }
|
128
|
+
let(:token_response) { { access_token: { } } }
|
94
129
|
let(:query_params) { nil }
|
95
130
|
let(:headers) { nil }
|
96
131
|
let(:auth_method) { :get }
|
@@ -105,15 +140,24 @@ describe "REST" do
|
|
105
140
|
|
106
141
|
let!(:auth_url_request_stub) do
|
107
142
|
stub = stub_request(auth_method, auth_url)
|
108
|
-
stub.with(:query =>
|
109
|
-
stub.with(:
|
110
|
-
stub.to_return(
|
143
|
+
stub.with(:query => query_params) unless query_params.nil?
|
144
|
+
stub.with(:headers => headers) unless headers.nil?
|
145
|
+
stub.to_return(
|
146
|
+
:status => 201,
|
147
|
+
:body => token_request.to_json,
|
148
|
+
:headers => { 'Content-Type' => 'application/json' }
|
149
|
+
)
|
111
150
|
end
|
112
151
|
|
113
152
|
let!(:request_token_stub) do
|
114
153
|
stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
|
115
|
-
with
|
116
|
-
|
154
|
+
with do |request|
|
155
|
+
request_body_includes(request, protocol, 'id', key_id)
|
156
|
+
end.to_return(
|
157
|
+
:status => 201,
|
158
|
+
:body => serialize(token_response, protocol),
|
159
|
+
:headers => { 'Content-Type' => content_type }
|
160
|
+
)
|
117
161
|
end
|
118
162
|
|
119
163
|
context 'valid' do
|
@@ -244,30 +288,30 @@ describe "REST" do
|
|
244
288
|
end
|
245
289
|
end
|
246
290
|
|
247
|
-
describe
|
291
|
+
describe '#create_token_request' do
|
248
292
|
let(:ttl) { 60 * 60 }
|
249
293
|
let(:capability) { { :foo => ["publish"] } }
|
250
294
|
let(:options) { Hash.new }
|
251
295
|
subject { auth.create_token_request(options) }
|
252
296
|
|
253
|
-
it
|
297
|
+
it 'uses the key ID from the client' do
|
254
298
|
expect(subject[:id]).to eql(key_id)
|
255
299
|
end
|
256
300
|
|
257
|
-
it
|
301
|
+
it 'uses the default TTL' do
|
258
302
|
expect(subject[:ttl]).to eql(Ably::Models::Token::DEFAULTS[:ttl])
|
259
303
|
end
|
260
304
|
|
261
|
-
it
|
305
|
+
it 'uses the default capability' do
|
262
306
|
expect(subject[:capability]).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
|
263
307
|
end
|
264
308
|
|
265
|
-
it
|
309
|
+
it 'has a unique nonce' do
|
266
310
|
unique_nonces = 100.times.map { auth.create_token_request[:nonce] }
|
267
311
|
expect(unique_nonces.uniq.length).to eql(100)
|
268
312
|
end
|
269
313
|
|
270
|
-
it
|
314
|
+
it 'has a nonce of at least 16 characters' do
|
271
315
|
expect(subject[:nonce].length).to be >= 16
|
272
316
|
end
|
273
317
|
|
@@ -283,7 +327,7 @@ describe "REST" do
|
|
283
327
|
end
|
284
328
|
end
|
285
329
|
|
286
|
-
context
|
330
|
+
context 'invalid attributes' do
|
287
331
|
let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
|
288
332
|
specify 'are ignored' do
|
289
333
|
expect(subject.keys).to_not include(:is_not_used_by_token_request)
|
@@ -292,19 +336,19 @@ describe "REST" do
|
|
292
336
|
end
|
293
337
|
end
|
294
338
|
|
295
|
-
context
|
339
|
+
context 'missing key ID and/or secret' do
|
296
340
|
let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
|
297
341
|
|
298
|
-
it
|
342
|
+
it 'should raise an exception if key secret is missing' do
|
299
343
|
expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
|
300
344
|
end
|
301
345
|
|
302
|
-
it
|
346
|
+
it 'should raise an exception if key id is missing' do
|
303
347
|
expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
|
304
348
|
end
|
305
349
|
end
|
306
350
|
|
307
|
-
context
|
351
|
+
context 'with :query_time option' do
|
308
352
|
let(:time) { Time.now - 30 }
|
309
353
|
let(:options) { { query_time: true } }
|
310
354
|
|
@@ -314,7 +358,7 @@ describe "REST" do
|
|
314
358
|
end
|
315
359
|
end
|
316
360
|
|
317
|
-
context
|
361
|
+
context 'with :timestamp option' do
|
318
362
|
let(:token_request_time) { Time.now + 5 }
|
319
363
|
let(:options) { { timestamp: token_request_time } }
|
320
364
|
|
@@ -323,7 +367,7 @@ describe "REST" do
|
|
323
367
|
end
|
324
368
|
end
|
325
369
|
|
326
|
-
context
|
370
|
+
context 'signing' do
|
327
371
|
let(:options) do
|
328
372
|
{
|
329
373
|
id: SecureRandom.hex,
|
@@ -342,7 +386,7 @@ describe "REST" do
|
|
342
386
|
end
|
343
387
|
end
|
344
388
|
|
345
|
-
context
|
389
|
+
context 'client with token authentication' do
|
346
390
|
let(:capability) { { :foo => ["publish"] } }
|
347
391
|
|
348
392
|
describe "with token_id argument" do
|
@@ -358,19 +402,19 @@ describe "REST" do
|
|
358
402
|
Ably::Rest::Client.new(token_id: token_id, environment: environment, protocol: protocol)
|
359
403
|
end
|
360
404
|
|
361
|
-
it
|
362
|
-
expect(token_auth_client.channel(
|
405
|
+
it 'authenticates successfully' do
|
406
|
+
expect(token_auth_client.channel('foo').publish('event', 'data')).to be_truthy
|
363
407
|
end
|
364
408
|
|
365
|
-
it
|
366
|
-
expect { token_auth_client.channel(
|
409
|
+
it 'disallows publishing on unspecified capability channels' do
|
410
|
+
expect { token_auth_client.channel('bar').publish('event', 'data') }.to raise_error do |error|
|
367
411
|
expect(error).to be_a(Ably::Exceptions::InvalidToken)
|
368
412
|
expect(error.status).to eql(401)
|
369
413
|
expect(error.code).to eql(40160)
|
370
414
|
end
|
371
415
|
end
|
372
416
|
|
373
|
-
it
|
417
|
+
it 'fails if timestamp is invalid' do
|
374
418
|
expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
|
375
419
|
expect(error).to be_a(Ably::Exceptions::InvalidToken)
|
376
420
|
expect(error.status).to eql(401)
|
@@ -379,7 +423,7 @@ describe "REST" do
|
|
379
423
|
end
|
380
424
|
end
|
381
425
|
|
382
|
-
describe
|
426
|
+
describe 'implicit through client id' do
|
383
427
|
let(:client_id) { '999' }
|
384
428
|
let(:client) do
|
385
429
|
Ably::Rest::Client.new(api_key: api_key, client_id: client_id, environment: environment, protocol: protocol)
|
@@ -404,25 +448,25 @@ describe "REST" do
|
|
404
448
|
to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
405
449
|
end
|
406
450
|
|
407
|
-
it
|
408
|
-
client.channel(
|
451
|
+
it 'will create a token request' do
|
452
|
+
client.channel('foo').publish('event', 'data')
|
409
453
|
expect(request_token_stub).to have_been_requested
|
410
454
|
end
|
411
455
|
end
|
412
456
|
|
413
|
-
context
|
457
|
+
context 'will create a token' do
|
414
458
|
let(:token) { client.auth.current_token }
|
415
459
|
|
416
|
-
it
|
460
|
+
it 'before a request is made' do
|
417
461
|
expect(token).to be_nil
|
418
462
|
end
|
419
463
|
|
420
|
-
it
|
421
|
-
expect(client.channel(
|
464
|
+
it 'when a message is published' do
|
465
|
+
expect(client.channel('foo').publish('event', 'data')).to be_truthy
|
422
466
|
end
|
423
467
|
|
424
|
-
it
|
425
|
-
client.channel(
|
468
|
+
it 'with capability and TTL defaults' do
|
469
|
+
client.channel('foo').publish('event', 'data')
|
426
470
|
|
427
471
|
expect(token).to be_a(Ably::Models::Token)
|
428
472
|
capability_with_str_key = Ably::Models::Token::DEFAULTS[:capability]
|