ably 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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]
|