ably-rest 0.7.5 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +2 -0
- data/README.md +41 -15
- data/SPEC.md +654 -518
- data/lib/submodules/ably-ruby/.gitignore +1 -0
- data/lib/submodules/ably-ruby/.gitmodules +3 -0
- data/lib/submodules/ably-ruby/README.md +54 -26
- data/lib/submodules/ably-ruby/SPEC.md +468 -322
- data/lib/submodules/ably-ruby/ably.gemspec +4 -2
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +185 -131
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +31 -44
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +1 -2
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +67 -24
- data/lib/submodules/ably-ruby/lib/ably/models/stats_types.rb +131 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +101 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +3 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +3 -7
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +32 -5
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +4 -8
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +5 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +3 -7
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +13 -10
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +19 -20
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +14 -12
- data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +74 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +3 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +18 -18
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +5 -5
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +12 -12
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +5 -5
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +56 -13
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +8 -8
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +262 -158
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +11 -9
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +28 -21
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +30 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +10 -10
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +93 -56
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +50 -45
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +3 -2
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +36 -28
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +3 -3
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +20 -33
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +18 -1
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +81 -72
- data/lib/submodules/ably-ruby/spec/unit/models/stats_spec.rb +289 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +110 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +8 -8
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +1 -1
- metadata +9 -7
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +0 -74
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +0 -56
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +0 -56
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +0 -113
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +0 -86
@@ -6,7 +6,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
6
6
|
|
7
7
|
vary_by_protocol do
|
8
8
|
let(:default_options) do
|
9
|
-
{
|
9
|
+
{ key: api_key, environment: environment, protocol: protocol }
|
10
10
|
end
|
11
11
|
|
12
12
|
let(:client_options) { default_options }
|
@@ -16,12 +16,12 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
16
16
|
|
17
17
|
context 'authentication failure' do
|
18
18
|
let(:client_options) do
|
19
|
-
default_options.merge(
|
19
|
+
default_options.merge(key: invalid_key, log_level: :none)
|
20
20
|
end
|
21
21
|
|
22
22
|
context 'when API key is invalid' do
|
23
23
|
context 'with invalid app part of the key' do
|
24
|
-
let(:invalid_key) { 'not_an_app.
|
24
|
+
let(:invalid_key) { 'not_an_app.invalid_key_name:invalid_key_value' }
|
25
25
|
|
26
26
|
it 'enters the failed state and returns a not found error' do
|
27
27
|
connection.on(:failed) do |error|
|
@@ -34,8 +34,8 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
context 'with invalid key
|
38
|
-
let(:invalid_key) { "#{app_id}.
|
37
|
+
context 'with invalid key name part of the key' do
|
38
|
+
let(:invalid_key) { "#{app_id}.invalid_key_name:invalid_key_value" }
|
39
39
|
|
40
40
|
it 'enters the failed state and returns an authorization error' do
|
41
41
|
connection.on(:failed) do |error|
|
@@ -7,7 +7,7 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
7
7
|
|
8
8
|
vary_by_protocol do
|
9
9
|
let(:default_options) do
|
10
|
-
{
|
10
|
+
{ key: api_key, environment: environment, protocol: protocol }
|
11
11
|
end
|
12
12
|
|
13
13
|
let(:client_options) { default_options }
|
@@ -52,7 +52,7 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
52
52
|
before do
|
53
53
|
# Reduce token expiry buffer to zero so that a token expired? predicate is exact
|
54
54
|
# Normally there is a buffer so that a token expiring soon is considered expired
|
55
|
-
stub_const 'Ably::Models::
|
55
|
+
stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0
|
56
56
|
end
|
57
57
|
|
58
58
|
context 'for renewable tokens' do
|
@@ -93,17 +93,17 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
93
93
|
|
94
94
|
it 'renews the token on connect' do
|
95
95
|
sleep ttl + 0.1
|
96
|
-
expect(client.auth.
|
96
|
+
expect(client.auth.current_token_details).to be_expired
|
97
97
|
expect(client.auth).to receive(:authorise).at_least(:once).and_call_original
|
98
98
|
connection.once(:connected) do
|
99
|
-
expect(client.auth.
|
99
|
+
expect(client.auth.current_token_details).to_not be_expired
|
100
100
|
stop_reactor
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
105
|
context 'with immediately expiring token' do
|
106
|
-
let(:ttl) { 0.
|
106
|
+
let(:ttl) { 0.001 }
|
107
107
|
|
108
108
|
it 'renews the token on connect, and only makes one subsequent attempt to obtain a new token' do
|
109
109
|
expect(client.auth).to receive(:authorise).at_least(:twice).and_call_original
|
@@ -138,19 +138,19 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
138
138
|
|
139
139
|
context 'when connected with a valid non-expired token' do
|
140
140
|
context 'that then expires following the connection being opened' do
|
141
|
-
let(:ttl) {
|
141
|
+
let(:ttl) { 5 }
|
142
142
|
let(:channel) { client.channel('test') }
|
143
143
|
|
144
144
|
context 'the server' do
|
145
|
-
it 'disconnects the client, and the client automatically renews the token and then reconnects', em_timeout:
|
146
|
-
original_token = client.auth.
|
145
|
+
it 'disconnects the client, and the client automatically renews the token and then reconnects', em_timeout: 15 do
|
146
|
+
original_token = client.auth.current_token_details
|
147
147
|
expect(original_token).to_not be_expired
|
148
148
|
|
149
149
|
connection.once(:connected) do
|
150
150
|
started_at = Time.now
|
151
151
|
connection.once(:disconnected) do |error|
|
152
152
|
connection.once(:connected) do
|
153
|
-
expect(client.auth.
|
153
|
+
expect(client.auth.current_token_details).to_not be_expired
|
154
154
|
expect(Time.now - started_at >= ttl)
|
155
155
|
expect(original_token).to be_expired
|
156
156
|
expect(error.code).to eql(40140) # token expired
|
@@ -172,16 +172,16 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
172
172
|
|
173
173
|
context 'for non-renewable tokens' do
|
174
174
|
context 'that are expired' do
|
175
|
-
let!(:
|
175
|
+
let!(:expired_token_details) do
|
176
176
|
Ably::Realtime::Client.new(default_options).auth.request_token(ttl: 0.01)
|
177
177
|
end
|
178
178
|
|
179
179
|
context 'opening a new connection' do
|
180
|
-
let(:client_options) { default_options.merge(
|
180
|
+
let(:client_options) { default_options.merge(key: nil, token: expired_token_details.token, log_level: :none) }
|
181
181
|
|
182
182
|
it 'transitions state to failed', em_timeout: 10 do
|
183
183
|
EventMachine.add_timer(1) do # wait for token to expire
|
184
|
-
expect(
|
184
|
+
expect(expired_token_details).to be_expired
|
185
185
|
connection.once(:connected) { raise 'Connection should never connect as token has expired' }
|
186
186
|
connection.once(:failed) do
|
187
187
|
expect(client.connection.error_reason.code).to eql(40140)
|
@@ -6,7 +6,7 @@ require 'securerandom'
|
|
6
6
|
|
7
7
|
describe 'Ably::Realtime::Channel Message', :event_machine do
|
8
8
|
vary_by_protocol do
|
9
|
-
let(:default_options) { options.merge(
|
9
|
+
let(:default_options) { options.merge(key: api_key, environment: environment, protocol: protocol) }
|
10
10
|
let(:client_options) { default_options }
|
11
11
|
let(:client) do
|
12
12
|
Ably::Realtime::Client.new(client_options)
|
@@ -77,8 +77,8 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
77
77
|
context 'when retrieved over REST' do
|
78
78
|
it 'matches the sender connection#id' do
|
79
79
|
channel.publish('event', payload) do
|
80
|
-
channel.history do |
|
81
|
-
expect(
|
80
|
+
channel.history do |page|
|
81
|
+
expect(page.items.first.connection_id).to eql(client.connection.id)
|
82
82
|
stop_reactor
|
83
83
|
end
|
84
84
|
end
|
@@ -175,7 +175,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
175
175
|
|
176
176
|
context 'without suitable publishing permissions' do
|
177
177
|
let(:restricted_client) do
|
178
|
-
Ably::Realtime::Client.new(options.merge(
|
178
|
+
Ably::Realtime::Client.new(options.merge(key: restricted_api_key, environment: environment, protocol: protocol))
|
179
179
|
end
|
180
180
|
let(:restricted_channel) { restricted_client.channel("cansubscribe:example") }
|
181
181
|
let(:payload) { 'Test message without permission to publish' }
|
@@ -293,7 +293,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
|
|
293
293
|
end
|
294
294
|
end
|
295
295
|
|
296
|
-
resources_root = File.expand_path('
|
296
|
+
resources_root = File.expand_path('../../../../lib/submodules/ably-common/test-resources', __FILE__)
|
297
297
|
|
298
298
|
def self.add_tests_for_data(data)
|
299
299
|
data['items'].each_with_index do |item, index|
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
describe Ably::Realtime::Presence, 'history', :event_machine do
|
5
5
|
vary_by_protocol do
|
6
|
-
let(:default_options) { {
|
6
|
+
let(:default_options) { { key: api_key, environment: environment, protocol: protocol } }
|
7
7
|
|
8
8
|
let(:channel_name) { "persisted:#{random_str(2)}" }
|
9
9
|
|
@@ -21,16 +21,16 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
|
|
21
21
|
it 'provides up to the moment presence history' do
|
22
22
|
presence_client_one.enter(data: data) do
|
23
23
|
presence_client_one.leave(data: leave_data) do
|
24
|
-
presence_client_one.history do |
|
25
|
-
expect(
|
24
|
+
presence_client_one.history do |history_page|
|
25
|
+
expect(history_page.items.count).to eql(2)
|
26
26
|
|
27
|
-
expect(
|
28
|
-
expect(
|
29
|
-
expect(
|
27
|
+
expect(history_page.items[1].action).to eq(:enter)
|
28
|
+
expect(history_page.items[1].client_id).to eq(client_one.client_id)
|
29
|
+
expect(history_page.items[1].data).to eql(data)
|
30
30
|
|
31
|
-
expect(
|
32
|
-
expect(
|
33
|
-
expect(
|
31
|
+
expect(history_page.items[0].action).to eq(:leave)
|
32
|
+
expect(history_page.items[0].client_id).to eq(client_one.client_id)
|
33
|
+
expect(history_page.items[0].data).to eql(leave_data)
|
34
34
|
|
35
35
|
stop_reactor
|
36
36
|
end
|
@@ -40,16 +40,59 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
|
|
40
40
|
|
41
41
|
it 'ensures REST presence history message IDs match ProtocolMessage wrapped message and connection IDs via Realtime' do
|
42
42
|
presence_client_one.subscribe(:enter) do |message|
|
43
|
-
presence_client_one.history do |
|
44
|
-
expect(
|
43
|
+
presence_client_one.history do |history_page|
|
44
|
+
expect(history_page.items.count).to eql(1)
|
45
45
|
|
46
|
-
expect(
|
47
|
-
expect(
|
46
|
+
expect(history_page.items[0].id).to eql(message.id)
|
47
|
+
expect(history_page.items[0].connection_id).to eql(message.connection_id)
|
48
48
|
stop_reactor
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
52
|
presence_client_one.enter(data: data)
|
53
53
|
end
|
54
|
+
|
55
|
+
context 'with option until_attach: true' do
|
56
|
+
let(:event) { random_str }
|
57
|
+
let(:presence_data_before_attach) { random_str }
|
58
|
+
let(:presence_data_after_attach) { random_str }
|
59
|
+
|
60
|
+
it 'retrieves all presence messages before channel was attached' do
|
61
|
+
presence_client_two.enter(data: presence_data_before_attach) do
|
62
|
+
presence_client_one.enter(data: presence_data_after_attach) do
|
63
|
+
presence_client_one.history(until_attach: true) do |presence_page|
|
64
|
+
expect(presence_page.items.count).to eql(1)
|
65
|
+
expect(presence_page.items.first.data).to eql(presence_data_before_attach)
|
66
|
+
stop_reactor
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'and two pages of messages' do
|
73
|
+
it 'retrieves two pages of messages before channel was attached' do
|
74
|
+
when_all(*10.times.map { |i| presence_client_two.enter_client("client:#{i}", data: presence_data_before_attach) }) do
|
75
|
+
when_all(*10.times.map { |i| presence_client_one.enter_client("client:#{i}", data: presence_data_after_attach) }) do
|
76
|
+
presence_client_one.history(until_attach: true, limit: 5) do |presence_page|
|
77
|
+
expect(presence_page.items.count).to eql(5)
|
78
|
+
expect(presence_page.items.map(&:data).uniq.first).to eql(presence_data_before_attach)
|
79
|
+
|
80
|
+
presence_page.next do |presence_next_page|
|
81
|
+
expect(presence_next_page.items.count).to eql(5)
|
82
|
+
expect(presence_next_page.items.map(&:data).uniq.first).to eql(presence_data_before_attach)
|
83
|
+
expect(presence_next_page).to be_last
|
84
|
+
stop_reactor
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'raises an exception unless state is attached' do
|
93
|
+
expect { presence_client_one.history(until_attach: true) }.to raise_error(ArgumentError, /not attached/)
|
94
|
+
stop_reactor
|
95
|
+
end
|
96
|
+
end
|
54
97
|
end
|
55
98
|
end
|
@@ -5,7 +5,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
5
5
|
include Ably::Modules::Conversions
|
6
6
|
|
7
7
|
vary_by_protocol do
|
8
|
-
let(:default_options) { {
|
8
|
+
let(:default_options) { { key: api_key, environment: environment, protocol: protocol } }
|
9
9
|
let(:client_options) { default_options }
|
10
10
|
|
11
11
|
let(:anonymous_client) { Ably::Realtime::Client.new(client_options) }
|
@@ -487,7 +487,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
487
487
|
|
488
488
|
context 'without necessary capabilities to join presence' do
|
489
489
|
let(:restricted_client) do
|
490
|
-
Ably::Realtime::Client.new(default_options.merge(
|
490
|
+
Ably::Realtime::Client.new(default_options.merge(key: restricted_api_key, log_level: :fatal))
|
491
491
|
end
|
492
492
|
let(:restricted_channel) { restricted_client.channel("cansubscribe:channel") }
|
493
493
|
let(:restricted_presence) { restricted_channel.presence }
|
@@ -688,7 +688,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
688
688
|
|
689
689
|
context 'without necessary capabilities to enter on behalf of another client' do
|
690
690
|
let(:restricted_client) do
|
691
|
-
Ably::Realtime::Client.new(default_options.merge(
|
691
|
+
Ably::Realtime::Client.new(default_options.merge(key: restricted_api_key, log_level: :fatal))
|
692
692
|
end
|
693
693
|
let(:restricted_channel) { restricted_client.channel("cansubscribe:channel") }
|
694
694
|
let(:restricted_presence) { restricted_channel.presence }
|
@@ -1123,8 +1123,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1123
1123
|
context 'REST #get' do
|
1124
1124
|
it 'returns current members' do
|
1125
1125
|
presence_client_one.enter(data: data_payload) do
|
1126
|
-
|
1127
|
-
this_member =
|
1126
|
+
members_page = channel_rest_client_one.presence.get
|
1127
|
+
this_member = members_page.items.first
|
1128
1128
|
|
1129
1129
|
expect(this_member).to be_a(Ably::Models::PresenceMessage)
|
1130
1130
|
expect(this_member.client_id).to eql(client_one.client_id)
|
@@ -1137,8 +1137,8 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1137
1137
|
it 'returns no members once left' do
|
1138
1138
|
presence_client_one.enter(data: data_payload) do
|
1139
1139
|
presence_client_one.leave do
|
1140
|
-
|
1141
|
-
expect(
|
1140
|
+
members_page = channel_rest_client_one.presence.get
|
1141
|
+
expect(members_page.items.count).to eql(0)
|
1142
1142
|
stop_reactor
|
1143
1143
|
end
|
1144
1144
|
end
|
@@ -1264,7 +1264,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
1264
1264
|
context 'REST #get' do
|
1265
1265
|
it 'returns a list of members with decrypted data' do
|
1266
1266
|
encrypted_channel.presence.enter(data: data) do
|
1267
|
-
member = channel_rest_client_one.presence.get.first
|
1267
|
+
member = channel_rest_client_one.presence.get.items.first
|
1268
1268
|
expect(member.encoding).to be_nil
|
1269
1269
|
expect(member.data).to eql(data)
|
1270
1270
|
stop_reactor
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Ably::Realtime::Client, '#stats', :event_machine do
|
4
4
|
vary_by_protocol do
|
5
5
|
let(:client) do
|
6
|
-
Ably::Realtime::Client.new(
|
6
|
+
Ably::Realtime::Client.new(key: api_key, environment: environment, protocol: protocol)
|
7
7
|
end
|
8
8
|
|
9
9
|
describe 'fetching stats' do
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Ably::Realtime::Client, '#time', :event_machine do
|
4
4
|
vary_by_protocol do
|
5
5
|
let(:client) do
|
6
|
-
Ably::Realtime::Client.new(
|
6
|
+
Ably::Realtime::Client.new(key: api_key, environment: environment, protocol: protocol)
|
7
7
|
end
|
8
8
|
|
9
9
|
describe 'fetching the service time' do
|
@@ -4,17 +4,17 @@ require 'spec_helper'
|
|
4
4
|
describe Ably::Auth do
|
5
5
|
include Ably::Modules::Conversions
|
6
6
|
|
7
|
-
def hmac_for(
|
8
|
-
|
7
|
+
def hmac_for(token_request_attributes, secret)
|
8
|
+
token_request= Ably::Models::IdiomaticRubyWrapper.new(token_request_attributes)
|
9
9
|
|
10
10
|
text = [
|
11
|
-
:
|
11
|
+
:key_name,
|
12
12
|
:ttl,
|
13
13
|
:capability,
|
14
14
|
:client_id,
|
15
15
|
:timestamp,
|
16
16
|
:nonce
|
17
|
-
].map { |key| "#{
|
17
|
+
].map { |key| "#{token_request.hash[key]}\n" }.join("")
|
18
18
|
|
19
19
|
encode64(
|
20
20
|
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
|
@@ -23,7 +23,7 @@ describe Ably::Auth do
|
|
23
23
|
|
24
24
|
vary_by_protocol do
|
25
25
|
let(:client) do
|
26
|
-
Ably::Rest::Client.new(
|
26
|
+
Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol)
|
27
27
|
end
|
28
28
|
let(:auth) { client.auth }
|
29
29
|
let(:content_type) do
|
@@ -52,37 +52,43 @@ describe Ably::Auth do
|
|
52
52
|
end
|
53
53
|
|
54
54
|
it 'has immutable options' do
|
55
|
-
expect { auth.options['
|
55
|
+
expect { auth.options['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
|
56
56
|
end
|
57
57
|
|
58
58
|
describe '#request_token' do
|
59
59
|
let(:ttl) { 30 * 60 }
|
60
60
|
let(:capability) { { :foo => ['publish'] } }
|
61
61
|
|
62
|
-
let(:
|
62
|
+
let(:token_details) do
|
63
63
|
auth.request_token(
|
64
64
|
ttl: ttl,
|
65
65
|
capability: capability
|
66
66
|
)
|
67
67
|
end
|
68
68
|
|
69
|
-
it 'returns a valid requested token in the expected format with valid
|
70
|
-
expect(token
|
71
|
-
expect(
|
72
|
-
expect(
|
73
|
-
expect(
|
69
|
+
it 'returns a valid requested token in the expected format with valid issued and expires attributes' do
|
70
|
+
expect(token_details.token).to match(/^#{app_id}\.[\w-]+$/)
|
71
|
+
expect(token_details.key_name).to match(/^#{key_name}$/)
|
72
|
+
expect(token_details.issued).to be_within(2).of(Time.now)
|
73
|
+
expect(token_details.expires).to be_within(2).of(Time.now + ttl)
|
74
74
|
end
|
75
75
|
|
76
76
|
%w(client_id capability nonce timestamp ttl).each do |option|
|
77
77
|
context "with option :#{option}", :webmock do
|
78
|
-
|
78
|
+
def coerce_if_time_value(field_name, value, options = {})
|
79
|
+
multiply = options[:multiply]
|
80
|
+
return value unless %w(timestamp ttl).include?(field_name)
|
81
|
+
value.to_i * (multiply ? multiply : 1)
|
82
|
+
end
|
83
|
+
|
84
|
+
let(:random) { coerce_if_time_value(option, random_int_str) }
|
79
85
|
let(:options) { { option.to_sym => random } }
|
80
86
|
|
81
|
-
let(:token_response) { {
|
87
|
+
let(:token_response) { {} }
|
82
88
|
let!(:request_token_stub) do
|
83
|
-
stub_request(:post, "#{client.endpoint}/keys/#{
|
89
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
|
84
90
|
with do |request|
|
85
|
-
request_body_includes(request, protocol, option, random)
|
91
|
+
request_body_includes(request, protocol, option, coerce_if_time_value(option, random, multiply: 1000))
|
86
92
|
end.to_return(
|
87
93
|
:status => 201,
|
88
94
|
:body => serialize(token_response, protocol),
|
@@ -92,25 +98,25 @@ describe Ably::Auth do
|
|
92
98
|
|
93
99
|
before { auth.request_token options }
|
94
100
|
|
95
|
-
it
|
101
|
+
it "overrides default and uses camelCase notation for attributes" do
|
96
102
|
expect(request_token_stub).to have_been_requested
|
97
103
|
end
|
98
104
|
end
|
99
105
|
end
|
100
106
|
|
101
|
-
context 'with :
|
102
|
-
let(:
|
107
|
+
context 'with :key option', :webmock do
|
108
|
+
let(:key_name) { "app.#{random_str}" }
|
103
109
|
let(:key_secret) { random_str }
|
104
110
|
let(:nonce) { random_str }
|
105
|
-
let(:token_options) { {
|
111
|
+
let(:token_options) { { key: "#{key_name}:#{key_secret}", nonce: nonce, timestamp: Time.now } }
|
106
112
|
let(:token_request) { auth.create_token_request(token_options) }
|
107
113
|
let(:mac) do
|
108
114
|
hmac_for(token_request, key_secret)
|
109
115
|
end
|
110
116
|
|
111
|
-
let(:token_response) { {
|
117
|
+
let(:token_response) { {} }
|
112
118
|
let!(:request_token_stub) do
|
113
|
-
stub_request(:post, "#{client.endpoint}/keys/#{
|
119
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
|
114
120
|
with do |request|
|
115
121
|
request_body_includes(request, protocol, 'mac', mac)
|
116
122
|
end.to_return(
|
@@ -119,9 +125,38 @@ describe Ably::Auth do
|
|
119
125
|
:headers => { 'Content-Type' => content_type })
|
120
126
|
end
|
121
127
|
|
122
|
-
let!(:token) { auth.request_token(token_options) }
|
128
|
+
let!(:token) { puts token_options; auth.request_token(token_options) }
|
123
129
|
|
124
|
-
specify '
|
130
|
+
specify 'key_name is used in request and signing uses key_secret' do
|
131
|
+
expect(request_token_stub).to have_been_requested
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'with :key_name & :key_secret options', :webmock do
|
136
|
+
let(:key_name) { "app.#{random_str}" }
|
137
|
+
let(:key_secret) { random_str }
|
138
|
+
let(:nonce) { random_str }
|
139
|
+
|
140
|
+
let(:name_secret_token_options) { { key_name: key_name, key_secret: key_secret, nonce: nonce, timestamp: Time.now } }
|
141
|
+
let(:token_request) { auth.create_token_request(name_secret_token_options) }
|
142
|
+
let(:mac) do
|
143
|
+
hmac_for(token_request, key_secret)
|
144
|
+
end
|
145
|
+
|
146
|
+
let(:token_response) { {} }
|
147
|
+
let!(:request_token_stub) do
|
148
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
|
149
|
+
with do |request|
|
150
|
+
request_body_includes(request, protocol, 'mac', mac)
|
151
|
+
end.to_return(
|
152
|
+
:status => 201,
|
153
|
+
:body => serialize(token_response, protocol),
|
154
|
+
:headers => { 'Content-Type' => content_type })
|
155
|
+
end
|
156
|
+
|
157
|
+
let!(:token) { auth.request_token(name_secret_token_options); }
|
158
|
+
|
159
|
+
specify 'key_name is used in request and signing uses key_secret' do
|
125
160
|
expect(request_token_stub).to have_been_requested
|
126
161
|
end
|
127
162
|
end
|
@@ -146,8 +181,8 @@ describe Ably::Auth do
|
|
146
181
|
|
147
182
|
context 'with :auth_url option', :webmock do
|
148
183
|
let(:auth_url) { 'https://www.fictitious.com/get_token' }
|
149
|
-
let(:auth_url_response) { {
|
150
|
-
let(:token_response) { {
|
184
|
+
let(:auth_url_response) { { keyName: key_name }.to_json }
|
185
|
+
let(:token_response) { {} }
|
151
186
|
let(:query_params) { nil }
|
152
187
|
let(:headers) { nil }
|
153
188
|
let(:auth_method) { :get }
|
@@ -166,15 +201,16 @@ describe Ably::Auth do
|
|
166
201
|
stub.with(:headers => headers) unless headers.nil?
|
167
202
|
stub.to_return(
|
168
203
|
:status => 201,
|
169
|
-
:body => auth_url_response
|
170
|
-
:headers => { 'Content-Type' =>
|
204
|
+
:body => auth_url_response,
|
205
|
+
:headers => { 'Content-Type' => auth_url_content_type }
|
171
206
|
)
|
172
207
|
end
|
208
|
+
let(:auth_url_content_type) { 'application/json' }
|
173
209
|
|
174
210
|
let!(:request_token_stub) do
|
175
|
-
stub_request(:post, "#{client.endpoint}/keys/#{
|
211
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
|
176
212
|
with do |request|
|
177
|
-
request_body_includes(request, protocol, '
|
213
|
+
request_body_includes(request, protocol, 'key_name', key_name)
|
178
214
|
end.to_return(
|
179
215
|
:status => 201,
|
180
216
|
:body => serialize(token_response, protocol),
|
@@ -191,7 +227,7 @@ describe Ably::Auth do
|
|
191
227
|
end
|
192
228
|
|
193
229
|
it 'returns a valid token generated from the token request' do
|
194
|
-
expect(token).to be_a(Ably::Models::
|
230
|
+
expect(token).to be_a(Ably::Models::TokenDetails)
|
195
231
|
end
|
196
232
|
|
197
233
|
context 'with :query_params' do
|
@@ -220,29 +256,45 @@ describe Ably::Auth do
|
|
220
256
|
end
|
221
257
|
end
|
222
258
|
|
223
|
-
context 'when response from :auth_url is a token' do
|
224
|
-
let(:
|
225
|
-
let(:
|
259
|
+
context 'when response from :auth_url is a token details object' do
|
260
|
+
let(:token) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
|
261
|
+
let(:issued) { Time.now }
|
226
262
|
let(:expires) { Time.now + 60}
|
227
263
|
let(:capability) { {'foo'=>['publish']} }
|
264
|
+
let(:capability_str) { JSON.dump(capability) }
|
228
265
|
let(:auth_url_response) do
|
229
266
|
{
|
230
|
-
'
|
231
|
-
'
|
232
|
-
'
|
233
|
-
'expires' => expires.to_i,
|
234
|
-
'capability'=>
|
235
|
-
}
|
267
|
+
'token' => token,
|
268
|
+
'key_name' => 'J_0Tlg.NxCRig',
|
269
|
+
'issued' => issued.to_i * 1000,
|
270
|
+
'expires' => expires.to_i * 1000,
|
271
|
+
'capability'=> capability_str
|
272
|
+
}.to_json
|
236
273
|
end
|
237
274
|
|
238
|
-
let!(:
|
275
|
+
let!(:token_details) { auth.request_token(options) }
|
239
276
|
|
240
|
-
it 'returns
|
277
|
+
it 'returns TokenDetails created from the token JSON' do
|
241
278
|
expect(request_token_stub).to_not have_been_requested
|
242
|
-
expect(
|
243
|
-
expect(token
|
244
|
-
expect(
|
245
|
-
expect(
|
279
|
+
expect(token_details).to be_a(Ably::Models::TokenDetails)
|
280
|
+
expect(token_details.token).to eql(token)
|
281
|
+
expect(token_details.expires).to be_within(1).of(expires)
|
282
|
+
expect(token_details.issued).to be_within(1).of(issued)
|
283
|
+
expect(token_details.capability).to eql(capability)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context 'when response from :auth_url is text/plain content type and a token string' do
|
288
|
+
let(:token) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
|
289
|
+
let(:auth_url_content_type) { 'text/plain' }
|
290
|
+
let(:auth_url_response) { token }
|
291
|
+
|
292
|
+
let!(:token_details) { auth.request_token(options) }
|
293
|
+
|
294
|
+
it 'returns TokenDetails created from the token JSON' do
|
295
|
+
expect(request_token_stub).to_not have_been_requested
|
296
|
+
expect(token_details).to be_a(Ably::Models::TokenDetails)
|
297
|
+
expect(token_details.token).to eql(token)
|
246
298
|
end
|
247
299
|
end
|
248
300
|
|
@@ -270,90 +322,135 @@ describe Ably::Auth do
|
|
270
322
|
end
|
271
323
|
end
|
272
324
|
|
273
|
-
context 'with
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
325
|
+
context 'with a Proc for the :auth_callback option' do
|
326
|
+
context 'that returns a TokenRequest' do
|
327
|
+
let(:client_id) { random_str }
|
328
|
+
let(:options) { { client_id: client_id } }
|
329
|
+
let!(:request_token) do
|
330
|
+
auth.request_token(options.merge(auth_callback: Proc.new do |block_options|
|
331
|
+
@block_called = true
|
332
|
+
@block_options = block_options
|
333
|
+
auth.create_token_request(client_id: client_id)
|
334
|
+
end))
|
281
335
|
end
|
282
|
-
end
|
283
336
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
337
|
+
it 'calls the Proc when authenticating to obtain the request token' do
|
338
|
+
expect(@block_called).to eql(true)
|
339
|
+
expect(@block_options).to include(options)
|
340
|
+
end
|
288
341
|
|
289
|
-
|
290
|
-
|
342
|
+
it 'uses the token request returned from the callback when requesting a new token' do
|
343
|
+
expect(request_token.client_id).to eql(client_id)
|
344
|
+
end
|
291
345
|
end
|
292
|
-
end
|
293
346
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
347
|
+
context 'that returns a TokenDetails JSON object' do
|
348
|
+
let(:client_id) { random_str }
|
349
|
+
let(:options) { { client_id: client_id } }
|
350
|
+
let(:token) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
|
351
|
+
let(:issued) { Time.now }
|
352
|
+
let(:expires) { Time.now + 60}
|
353
|
+
let(:capability) { {'foo'=>['publish']} }
|
354
|
+
let(:capability_str) { JSON.dump(capability) }
|
355
|
+
|
356
|
+
let!(:token_details) do
|
357
|
+
auth.request_token(options.merge(auth_callback: Proc.new do |block_options|
|
358
|
+
@block_called = true
|
359
|
+
@block_options = block_options
|
360
|
+
{
|
361
|
+
'token' => token,
|
362
|
+
'keyName' => 'J_0Tlg.NxCRig',
|
363
|
+
'clientId' => client_id,
|
364
|
+
'issued' => issued.to_i * 1000,
|
365
|
+
'expires' => expires.to_i * 1000,
|
366
|
+
'capability'=> capability_str
|
367
|
+
}
|
368
|
+
end))
|
369
|
+
end
|
301
370
|
|
302
|
-
|
303
|
-
|
304
|
-
@
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
371
|
+
it 'calls the Proc when authenticating to obtain the request token' do
|
372
|
+
expect(@block_called).to eql(true)
|
373
|
+
expect(@block_options).to include(options)
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'uses the token request returned from the callback when requesting a new token' do
|
377
|
+
expect(token_details).to be_a(Ably::Models::TokenDetails)
|
378
|
+
expect(token_details.token).to eql(token)
|
379
|
+
expect(token_details.client_id).to eql(client_id)
|
380
|
+
expect(token_details.expires).to be_within(1).of(expires)
|
381
|
+
expect(token_details.issued).to be_within(1).of(issued)
|
382
|
+
expect(token_details.capability).to eql(capability)
|
314
383
|
end
|
315
384
|
end
|
316
385
|
|
317
|
-
|
318
|
-
|
319
|
-
|
386
|
+
context 'that returns a TokenDetails object' do
|
387
|
+
let(:client_id) { random_str }
|
388
|
+
|
389
|
+
let!(:token_details) do
|
390
|
+
auth.request_token(auth_callback: Proc.new do |block_options|
|
391
|
+
auth.create_token_request({
|
392
|
+
client_id: client_id
|
393
|
+
})
|
394
|
+
end)
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'uses the token request returned from the callback when requesting a new token' do
|
398
|
+
expect(token_details).to be_a(Ably::Models::TokenDetails)
|
399
|
+
expect(token_details.client_id).to eql(client_id)
|
400
|
+
end
|
320
401
|
end
|
321
402
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
403
|
+
context 'that returns a Token string' do
|
404
|
+
let(:second_client) { Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol) }
|
405
|
+
let(:token) { second_client.auth.request_token.token }
|
406
|
+
|
407
|
+
let!(:token_details) do
|
408
|
+
auth.request_token(auth_callback: Proc.new do |block_options|
|
409
|
+
token
|
410
|
+
end)
|
411
|
+
end
|
412
|
+
|
413
|
+
it 'uses the token request returned from the callback when requesting a new token' do
|
414
|
+
expect(token_details).to be_a(Ably::Models::TokenDetails)
|
415
|
+
expect(token_details.token).to eql(token)
|
416
|
+
end
|
329
417
|
end
|
330
418
|
end
|
331
419
|
|
332
420
|
context 'persisted option', api_private: true do
|
333
421
|
context 'when set to true', api_private: true do
|
334
422
|
let(:options) { { persisted: true } }
|
335
|
-
let(:
|
423
|
+
let(:token_details) { auth.request_token(options) }
|
336
424
|
|
337
425
|
it 'returns a token with a short token ID that is used to look up the token details' do
|
338
|
-
expect(token.
|
339
|
-
expect(token
|
426
|
+
expect(token_details.token.length).to be < 64
|
427
|
+
expect(token_details.token).to match(/^#{app_id}\.A/)
|
340
428
|
end
|
341
429
|
end
|
342
430
|
|
343
431
|
context 'when omitted', api_private: true do
|
344
432
|
let(:options) { { } }
|
345
|
-
let(:
|
433
|
+
let(:token_details) { auth.request_token(options) }
|
346
434
|
|
347
435
|
it 'returns a literal token' do
|
348
|
-
expect(token.
|
436
|
+
expect(token_details.token.length).to be > 64
|
349
437
|
end
|
350
438
|
end
|
351
439
|
end
|
440
|
+
|
441
|
+
context 'with client_id' do
|
442
|
+
let(:client_id) { random_str }
|
443
|
+
let(:token_details) { auth.request_token(client_id: client_id) }
|
444
|
+
|
445
|
+
it 'returns a token with the client_id' do
|
446
|
+
expect(token_details.client_id).to eql(client_id)
|
447
|
+
end
|
448
|
+
end
|
352
449
|
end
|
353
450
|
|
354
451
|
context 'before #authorise has been called' do
|
355
|
-
it 'has no
|
356
|
-
expect(auth.
|
452
|
+
it 'has no current_token_details' do
|
453
|
+
expect(auth.current_token_details).to be_nil
|
357
454
|
end
|
358
455
|
end
|
359
456
|
|
@@ -369,62 +466,62 @@ describe Ably::Auth do
|
|
369
466
|
end
|
370
467
|
|
371
468
|
it 'returns a valid token' do
|
372
|
-
expect(auth.authorise).to be_a(Ably::Models::
|
469
|
+
expect(auth.authorise).to be_a(Ably::Models::TokenDetails)
|
373
470
|
end
|
374
471
|
|
375
472
|
it 'issues a new token if option :force => true' do
|
376
|
-
expect { auth.authorise(force: true) }.to change { auth.
|
473
|
+
expect { auth.authorise(force: true) }.to change { auth.current_token_details }
|
377
474
|
end
|
378
475
|
end
|
379
476
|
|
380
477
|
context 'with previous authorisation' do
|
381
478
|
before do
|
382
479
|
auth.authorise
|
383
|
-
expect(auth.
|
480
|
+
expect(auth.current_token_details).to_not be_expired
|
384
481
|
end
|
385
482
|
|
386
|
-
it 'does not request a token if
|
483
|
+
it 'does not request a token if current_token_details has not expired' do
|
387
484
|
expect(auth).to_not receive(:request_token)
|
388
485
|
auth.authorise
|
389
486
|
end
|
390
487
|
|
391
488
|
it 'requests a new token if token is expired' do
|
392
|
-
allow(auth.
|
489
|
+
allow(auth.current_token_details).to receive(:expired?).and_return(true)
|
393
490
|
expect(auth).to receive(:request_token)
|
394
|
-
expect { auth.authorise }.to change { auth.
|
491
|
+
expect { auth.authorise }.to change { auth.current_token_details }
|
395
492
|
end
|
396
493
|
|
397
494
|
it 'issues a new token if option :force => true' do
|
398
|
-
expect { auth.authorise(force: true) }.to change { auth.
|
495
|
+
expect { auth.authorise(force: true) }.to change { auth.current_token_details }
|
399
496
|
end
|
400
497
|
end
|
401
498
|
|
402
|
-
it 'updates the persisted auth options
|
499
|
+
it 'updates the persisted auth options that are then used for subsequent authorise requests' do
|
403
500
|
expect(auth.options[:ttl]).to_not eql(26)
|
404
501
|
auth.authorise(ttl: 26)
|
405
502
|
expect(auth.options[:ttl]).to eql(26)
|
406
503
|
end
|
407
504
|
|
408
|
-
context 'with
|
505
|
+
context 'with a Proc for the :auth_callback option' do
|
409
506
|
let(:client_id) { random_str }
|
410
507
|
let!(:token) do
|
411
|
-
auth.authorise do
|
508
|
+
auth.authorise(auth_callback: Proc.new do
|
412
509
|
@block_called ||= 0
|
413
510
|
@block_called += 1
|
414
511
|
auth.create_token_request(client_id: client_id)
|
415
|
-
end
|
512
|
+
end)
|
416
513
|
end
|
417
514
|
|
418
|
-
it 'calls the
|
515
|
+
it 'calls the Proc' do
|
419
516
|
expect(@block_called).to eql(1)
|
420
517
|
end
|
421
518
|
|
422
|
-
it 'uses the token request returned from the
|
519
|
+
it 'uses the token request returned from the callback when requesting a new token' do
|
423
520
|
expect(token.client_id).to eql(client_id)
|
424
521
|
end
|
425
522
|
|
426
523
|
context 'for every subsequent #request_token' do
|
427
|
-
context 'without a
|
524
|
+
context 'without a :auth_callback Proc' do
|
428
525
|
it 'calls the originally provided block' do
|
429
526
|
auth.request_token
|
430
527
|
expect(@block_called).to eql(2)
|
@@ -432,8 +529,8 @@ describe Ably::Auth do
|
|
432
529
|
end
|
433
530
|
|
434
531
|
context 'with a provided block' do
|
435
|
-
it 'does not call the originally provided
|
436
|
-
auth.request_token { @request_block_called = true; auth.create_token_request }
|
532
|
+
it 'does not call the originally provided Proc and calls the new #request_token :auth_callback Proc' do
|
533
|
+
auth.request_token(auth_callback: Proc.new { @request_block_called = true; auth.create_token_request })
|
437
534
|
expect(@block_called).to eql(1)
|
438
535
|
expect(@request_block_called).to eql(true)
|
439
536
|
end
|
@@ -445,19 +542,19 @@ describe Ably::Auth do
|
|
445
542
|
describe '#create_token_request' do
|
446
543
|
let(:ttl) { 60 * 60 }
|
447
544
|
let(:capability) { { :foo => ["publish"] } }
|
448
|
-
let(:
|
449
|
-
subject { auth.create_token_request(
|
545
|
+
let(:token_request_options) { Hash.new }
|
546
|
+
subject { auth.create_token_request(token_request_options) }
|
450
547
|
|
451
|
-
it 'uses the key
|
452
|
-
expect(subject['
|
548
|
+
it 'uses the key name from the client' do
|
549
|
+
expect(subject['keyName']).to eql(key_name)
|
453
550
|
end
|
454
551
|
|
455
552
|
it 'uses the default TTL' do
|
456
|
-
expect(subject['ttl']).to eql(Ably::
|
553
|
+
expect(subject['ttl']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl) * 1000)
|
457
554
|
end
|
458
555
|
|
459
556
|
it 'uses the default capability' do
|
460
|
-
expect(subject['capability']).to eql(Ably::
|
557
|
+
expect(subject['capability']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:capability).to_json)
|
461
558
|
end
|
462
559
|
|
463
560
|
context 'the nonce' do
|
@@ -471,25 +568,25 @@ describe Ably::Auth do
|
|
471
568
|
end
|
472
569
|
end
|
473
570
|
|
474
|
-
%w(ttl
|
571
|
+
%w(ttl nonce client_id).each do |attribute|
|
475
572
|
context "with option :#{attribute}" do
|
476
|
-
let(:option_value) { random_int_str(1_000_000_000) }
|
573
|
+
let(:option_value) { random_int_str(1_000_000_000).to_i }
|
477
574
|
before do
|
478
|
-
|
575
|
+
token_request_options[attribute.to_sym] = option_value
|
479
576
|
end
|
480
577
|
it "overrides default" do
|
481
|
-
expect(subject
|
578
|
+
expect(subject.public_send(attribute).to_s).to eql(option_value.to_s)
|
482
579
|
end
|
483
580
|
end
|
484
581
|
end
|
485
582
|
|
486
583
|
context 'with additional invalid attributes' do
|
487
|
-
let(:
|
584
|
+
let(:token_request_options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
|
488
585
|
specify 'are ignored' do
|
489
|
-
expect(subject.keys).to_not include(:is_not_used_by_token_request)
|
490
|
-
expect(subject.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
|
491
|
-
expect(subject.keys).to include(
|
492
|
-
expect(subject
|
586
|
+
expect(subject.hash.keys).to_not include(:is_not_used_by_token_request)
|
587
|
+
expect(subject.hash.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
|
588
|
+
expect(subject.hash.keys).to include(:nonce)
|
589
|
+
expect(subject.nonce).to eql('valid')
|
493
590
|
end
|
494
591
|
end
|
495
592
|
|
@@ -497,47 +594,52 @@ describe Ably::Auth do
|
|
497
594
|
let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
|
498
595
|
|
499
596
|
it 'should raise an exception if key secret is missing' do
|
500
|
-
expect { auth.create_token_request(
|
597
|
+
expect { auth.create_token_request(key_name: 'name') }.to raise_error Ably::Exceptions::TokenRequestError
|
501
598
|
end
|
502
599
|
|
503
|
-
it 'should raise an exception if key
|
600
|
+
it 'should raise an exception if key name is missing' do
|
504
601
|
expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
|
505
602
|
end
|
506
603
|
end
|
507
604
|
|
508
605
|
context 'with :query_time option' do
|
509
606
|
let(:time) { Time.now - 30 }
|
510
|
-
let(:
|
607
|
+
let(:token_request_options) { { query_time: true } }
|
511
608
|
|
512
609
|
it 'queries the server for the timestamp' do
|
513
610
|
expect(client).to receive(:time).and_return(time)
|
514
|
-
expect(subject['timestamp']).to
|
611
|
+
expect(subject['timestamp']).to be_within(1).of(time.to_f * 1000)
|
515
612
|
end
|
516
613
|
end
|
517
614
|
|
518
615
|
context 'with :timestamp option' do
|
519
616
|
let(:token_request_time) { Time.now + 5 }
|
520
|
-
let(:
|
617
|
+
let(:token_request_options) { { timestamp: token_request_time } }
|
521
618
|
|
522
619
|
it 'uses the provided timestamp in the token request' do
|
523
|
-
expect(subject['timestamp']).to
|
620
|
+
expect(subject['timestamp']).to be_within(1).of(token_request_time.to_f * 1000)
|
524
621
|
end
|
525
622
|
end
|
526
623
|
|
527
624
|
context 'signing' do
|
528
|
-
let(:
|
625
|
+
let(:token_request_options) do
|
529
626
|
{
|
530
|
-
|
531
|
-
ttl:
|
627
|
+
key_name: random_str,
|
628
|
+
ttl: random_int_str.to_i,
|
532
629
|
capability: random_str,
|
533
630
|
client_id: random_str,
|
534
|
-
timestamp: random_int_str,
|
631
|
+
timestamp: random_int_str.to_i,
|
535
632
|
nonce: random_str
|
536
633
|
}
|
537
634
|
end
|
538
635
|
|
636
|
+
# TokenRequest expects times in milliseconds, whereas create_token_request assumes Ruby default of seconds
|
637
|
+
let(:token_request_attributes) do
|
638
|
+
token_request_options.merge(timestamp: token_request_options[:timestamp] * 1000, ttl: token_request_options[:ttl] * 1000)
|
639
|
+
end
|
640
|
+
|
539
641
|
it 'generates a valid HMAC' do
|
540
|
-
hmac = hmac_for(
|
642
|
+
hmac = hmac_for(Ably::Models::TokenRequest(token_request_attributes).hash, key_secret)
|
541
643
|
expect(subject['mac']).to eql(hmac)
|
542
644
|
end
|
543
645
|
end
|
@@ -546,20 +648,20 @@ describe Ably::Auth do
|
|
546
648
|
context 'using token authentication' do
|
547
649
|
let(:capability) { { :foo => ["publish"] } }
|
548
650
|
|
549
|
-
describe 'with :
|
651
|
+
describe 'with :token option' do
|
550
652
|
let(:ttl) { 60 * 60 }
|
551
|
-
let(:
|
653
|
+
let(:token_details) do
|
552
654
|
auth.request_token(
|
553
655
|
ttl: ttl,
|
554
656
|
capability: capability
|
555
657
|
)
|
556
658
|
end
|
557
|
-
let(:
|
659
|
+
let(:token) { token_details.token }
|
558
660
|
let(:token_auth_client) do
|
559
|
-
Ably::Rest::Client.new(
|
661
|
+
Ably::Rest::Client.new(token: token, environment: environment, protocol: protocol)
|
560
662
|
end
|
561
663
|
|
562
|
-
it 'authenticates successfully using the provided :
|
664
|
+
it 'authenticates successfully using the provided :token' do
|
563
665
|
expect(token_auth_client.channel('foo').publish('event', 'data')).to be_truthy
|
564
666
|
end
|
565
667
|
|
@@ -587,25 +689,23 @@ describe Ably::Auth do
|
|
587
689
|
context 'when implicit as a result of using :client id' do
|
588
690
|
let(:client_id) { '999' }
|
589
691
|
let(:client) do
|
590
|
-
Ably::Rest::Client.new(
|
692
|
+
Ably::Rest::Client.new(key: api_key, client_id: client_id, environment: environment, protocol: protocol)
|
591
693
|
end
|
592
|
-
let(:
|
694
|
+
let(:token) { 'unique-token' }
|
593
695
|
let(:token_response) do
|
594
696
|
{
|
595
|
-
|
596
|
-
id: token_id
|
597
|
-
}
|
697
|
+
token: token
|
598
698
|
}.to_json
|
599
699
|
end
|
600
700
|
|
601
701
|
context 'and requests to the Ably server are mocked', :webmock do
|
602
702
|
let!(:request_token_stub) do
|
603
|
-
stub_request(:post, "#{client.endpoint}/keys/#{
|
703
|
+
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
|
604
704
|
to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
|
605
705
|
end
|
606
706
|
let!(:publish_message_stub) do
|
607
707
|
stub_request(:post, "#{client.endpoint}/channels/foo/publish").
|
608
|
-
with(headers: { 'Authorization' => "Bearer #{encode64(
|
708
|
+
with(headers: { 'Authorization' => "Bearer #{encode64(token)}" }).
|
609
709
|
to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
610
710
|
end
|
611
711
|
|
@@ -616,7 +716,7 @@ describe Ably::Auth do
|
|
616
716
|
end
|
617
717
|
|
618
718
|
describe 'a token is created' do
|
619
|
-
let(:token) { client.auth.
|
719
|
+
let(:token) { client.auth.current_token_details }
|
620
720
|
|
621
721
|
it 'before a request is made' do
|
622
722
|
expect(token).to be_nil
|
@@ -629,22 +729,26 @@ describe Ably::Auth do
|
|
629
729
|
it 'with capability and TTL defaults' do
|
630
730
|
client.channel('foo').publish('event', 'data')
|
631
731
|
|
632
|
-
expect(token).to be_a(Ably::Models::
|
633
|
-
capability_with_str_key = Ably::
|
634
|
-
capability = Hash[capability_with_str_key.keys.map(&:
|
732
|
+
expect(token).to be_a(Ably::Models::TokenDetails)
|
733
|
+
capability_with_str_key = Ably::Auth::TOKEN_DEFAULTS.fetch(:capability)
|
734
|
+
capability = Hash[capability_with_str_key.keys.map(&:to_s).zip(capability_with_str_key.values)]
|
635
735
|
expect(token.capability).to eq(capability)
|
636
|
-
expect(token.
|
736
|
+
expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl))
|
637
737
|
expect(token.client_id).to eq(client_id)
|
638
738
|
end
|
639
739
|
end
|
640
740
|
end
|
641
741
|
end
|
642
742
|
|
643
|
-
context 'when using an :
|
743
|
+
context 'when using an :key and basic auth' do
|
644
744
|
specify '#using_token_auth? is false' do
|
645
745
|
expect(auth).to_not be_using_token_auth
|
646
746
|
end
|
647
747
|
|
748
|
+
specify '#key attribute contains the key string' do
|
749
|
+
expect(auth.key).to eql(api_key)
|
750
|
+
end
|
751
|
+
|
648
752
|
specify '#using_basic_auth? is true' do
|
649
753
|
expect(auth).to be_using_basic_auth
|
650
754
|
end
|