ably 0.7.6 → 0.8.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -7
  3. data/SPEC.md +310 -269
  4. data/lib/ably/auth.rb +177 -127
  5. data/lib/ably/models/presence_message.rb +1 -1
  6. data/lib/ably/models/protocol_message.rb +1 -2
  7. data/lib/ably/models/token_details.rb +101 -0
  8. data/lib/ably/models/token_request.rb +108 -0
  9. data/lib/ably/modules/http_helpers.rb +1 -1
  10. data/lib/ably/realtime.rb +2 -6
  11. data/lib/ably/realtime/channel.rb +14 -8
  12. data/lib/ably/realtime/client.rb +2 -6
  13. data/lib/ably/realtime/connection.rb +4 -2
  14. data/lib/ably/rest.rb +2 -6
  15. data/lib/ably/rest/channel.rb +10 -6
  16. data/lib/ably/rest/client.rb +15 -16
  17. data/lib/ably/rest/presence.rb +12 -10
  18. data/lib/ably/version.rb +1 -1
  19. data/spec/acceptance/realtime/client_spec.rb +15 -15
  20. data/spec/acceptance/realtime/connection_failures_spec.rb +3 -3
  21. data/spec/acceptance/realtime/connection_spec.rb +9 -9
  22. data/spec/acceptance/rest/auth_spec.rb +248 -172
  23. data/spec/acceptance/rest/base_spec.rb +8 -6
  24. data/spec/acceptance/rest/channel_spec.rb +9 -2
  25. data/spec/acceptance/rest/client_spec.rb +21 -21
  26. data/spec/acceptance/rest/presence_spec.rb +12 -5
  27. data/spec/acceptance/rest/stats_spec.rb +4 -4
  28. data/spec/rspec_config.rb +3 -2
  29. data/spec/shared/client_initializer_behaviour.rb +21 -24
  30. data/spec/support/api_helper.rb +3 -3
  31. data/spec/support/test_app.rb +9 -9
  32. data/spec/unit/auth_spec.rb +17 -0
  33. data/spec/unit/models/token_details_spec.rb +111 -0
  34. data/spec/unit/models/token_request_spec.rb +110 -0
  35. data/spec/unit/rest/client_spec.rb +1 -1
  36. metadata +8 -5
  37. data/lib/ably/models/token.rb +0 -74
  38. data/spec/unit/models/token_spec.rb +0 -86
@@ -23,15 +23,14 @@ module Ably
23
23
  # Obtain the set of members currently present for a channel
24
24
  #
25
25
  # @param [Hash] options the options for the set of members present
26
- # @option options [Integer,Time] :start Time or millisecond since epoch
27
- # @option options [Integer,Time] :end Time or millisecond since epoch
28
- # @option options [Symbol] :direction `:forwards` or `:backwards`
29
- # @option options [Integer] :limit Maximum number of members to retrieve up to 10,000
26
+ # @option options [Integer] :limit Maximum number of members to retrieve up to 1,000, defaults to 100
30
27
  #
31
28
  # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
32
29
  #
33
30
  def get(options = {})
34
- options = options.dup
31
+ options = options = {
32
+ :limit => 100
33
+ }.merge(options)
35
34
 
36
35
  paginated_options = {
37
36
  coerce_into: 'Ably::Models::PresenceMessage',
@@ -50,16 +49,19 @@ module Ably
50
49
  # Return the presence messages history for the channel
51
50
  #
52
51
  # @param [Hash] options the options for the message history request
53
- # @option options [Integer,Time] :start Time or millisecond since epoch
54
- # @option options [Integer,Time] :end Time or millisecond since epoch
55
- # @option options [Symbol] :direction `:forwards` or `:backwards`
56
- # @option options [Integer] :limit Maximum number of presence messages to retrieve up to 10,000
52
+ # @option options [Integer,Time] :start Ensure earliest time or millisecond since epoch for any presence messages retrieved is +:start+
53
+ # @option options [Integer,Time] :end Ensure latest time or millisecond since epoch for any presence messages retrieved is +:end+
54
+ # @option options [Symbol] :direction +:forwards+ or +:backwards+, defaults to +:backwards+
55
+ # @option options [Integer] :limit Maximum number of messages to retrieve up to 1,000, defaults to 100
57
56
  #
58
57
  # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResource page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResource#items #items}.
59
58
  #
60
59
  def history(options = {})
61
60
  url = "#{base_path}/history"
62
- options = options.dup
61
+ options = options = {
62
+ :direction => :backwards,
63
+ :limit => 100
64
+ }.merge(options)
63
65
 
64
66
  [:start, :end].each { |option| options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
65
67
 
@@ -1,3 +1,3 @@
1
1
  module Ably
2
- VERSION = '0.7.6'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -17,9 +17,9 @@ describe Ably::Realtime::Client, :event_machine do
17
17
  context 'basic auth' do
18
18
  it 'is enabled by default with a provided :key option' do
19
19
  connection.on(:connected) do
20
- expect(auth_params[:key_id]).to_not be_nil
20
+ expect(auth_params[:key]).to_not be_nil
21
21
  expect(auth_params[:access_token]).to be_nil
22
- expect(subject.auth.current_token).to be_nil
22
+ expect(subject.auth.current_token_details).to be_nil
23
23
  stop_reactor
24
24
  end
25
25
  end
@@ -44,15 +44,15 @@ describe Ably::Realtime::Client, :event_machine do
44
44
  [true, false].each do |tls_enabled|
45
45
  context "with TLS #{tls_enabled ? 'enabled' : 'disabled'}" do
46
46
  let(:capability) { { :foo => ["publish"] } }
47
- let(:token) { Ably::Realtime::Client.new(default_options).auth.request_token(capability: capability) }
48
- let(:client_options) { default_options.merge(token_id: token.id) }
47
+ let(:token_details) { Ably::Realtime::Client.new(default_options).auth.request_token(capability: capability) }
48
+ let(:client_options) { default_options.merge(token: token_details.token) }
49
49
 
50
- context 'and a pre-generated Token provided with the :token_id option' do
50
+ context 'and a pre-generated Token provided with the :token option' do
51
51
  it 'connects using token auth' do
52
52
  connection.on(:connected) do
53
53
  expect(auth_params[:access_token]).to_not be_nil
54
- expect(auth_params[:key_id]).to be_nil
55
- expect(subject.auth.current_token).to be_nil
54
+ expect(auth_params[:key]).to be_nil
55
+ expect(subject.auth.current_token_details).to be_nil
56
56
  stop_reactor
57
57
  end
58
58
  end
@@ -63,7 +63,7 @@ describe Ably::Realtime::Client, :event_machine do
63
63
 
64
64
  it 'automatically authorises on connect and generates a token' do
65
65
  connection.on(:connected) do
66
- expect(subject.auth.current_token).to_not be_nil
66
+ expect(subject.auth.current_token_details).to_not be_nil
67
67
  expect(auth_params[:access_token]).to_not be_nil
68
68
  stop_reactor
69
69
  end
@@ -79,7 +79,7 @@ describe Ably::Realtime::Client, :event_machine do
79
79
  connection.on(:connected) do
80
80
  expect(connection.state).to eq(:connected)
81
81
  expect(auth_params[:access_token]).to_not be_nil
82
- expect(auth_params[:key_id]).to be_nil
82
+ expect(auth_params[:key]).to be_nil
83
83
  stop_reactor
84
84
  end
85
85
  end
@@ -88,27 +88,27 @@ describe Ably::Realtime::Client, :event_machine do
88
88
  end
89
89
  end
90
90
 
91
- context 'with token_request_block' do
91
+ context 'with a Proc for the :auth_callback option' do
92
92
  let(:client_id) { random_str }
93
93
  let(:auth) { subject.auth }
94
94
 
95
95
  subject do
96
- Ably::Realtime::Client.new(client_options) do
96
+ Ably::Realtime::Client.new(client_options.merge(auth_callback: Proc.new do
97
97
  @block_called = true
98
98
  auth.create_token_request(client_id: client_id)
99
- end
99
+ end))
100
100
  end
101
101
 
102
- it 'calls the block' do
102
+ it 'calls the Proc' do
103
103
  connection.on(:connected) do
104
104
  expect(@block_called).to eql(true)
105
105
  stop_reactor
106
106
  end
107
107
  end
108
108
 
109
- it 'uses the token request when requesting a new token' do
109
+ it 'uses the token request returned from the callback when requesting a new token' do
110
110
  connection.on(:connected) do
111
- expect(auth.current_token.client_id).to eql(client_id)
111
+ expect(auth.current_token_details.client_id).to eql(client_id)
112
112
  stop_reactor
113
113
  end
114
114
  end
@@ -21,7 +21,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
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.invalid_key_id:invalid_key_value' }
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 ID part of the key' do
38
- let(:invalid_key) { "#{app_id}.invalid_key_id:invalid_key_value" }
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|
@@ -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::Token::TOKEN_EXPIRY_BUFFER', 0
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.current_token).to be_expired
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.current_token).to_not be_expired
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.01 }
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
@@ -143,14 +143,14 @@ describe Ably::Realtime::Connection, :event_machine do
143
143
 
144
144
  context 'the server' do
145
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
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.current_token).to_not be_expired
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!(:expired_token) do
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(key: nil, token_id: expired_token.id, log_level: :none) }
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(expired_token).to be_expired
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)
@@ -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(token_request, secret)
8
- ruby_named_token_request = Ably::Models::IdiomaticRubyWrapper.new(token_request)
7
+ def hmac_for(token_request_attributes, secret)
8
+ token_request= Ably::Models::IdiomaticRubyWrapper.new(token_request_attributes)
9
9
 
10
10
  text = [
11
- :id,
11
+ :key_name,
12
12
  :ttl,
13
13
  :capability,
14
14
  :client_id,
15
15
  :timestamp,
16
16
  :nonce
17
- ].map { |key| "#{ruby_named_token_request[key]}\n" }.join("")
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)
@@ -52,37 +52,42 @@ describe Ably::Auth do
52
52
  end
53
53
 
54
54
  it 'has immutable options' do
55
- expect { auth.options['key_id'] = 'new_id' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
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(:token) do
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 issued_at and expires_at attributes' do
70
- expect(token.id).to match(/^#{app_id}\.[\w-]+$/)
71
- expect(token.key_id).to match(/^#{key_id}$/)
72
- expect(token.issued_at).to be_within(2).of(Time.now)
73
- expect(token.expires_at).to be_within(2).of(Time.now + ttl)
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
- let(:random) { random_int_str }
78
+ def coerce_if_time_value(field_name, value, multiply: false)
79
+ return value unless %w(timestamp ttl).include?(field_name)
80
+ value.to_i * (multiply ? multiply : 1)
81
+ end
82
+
83
+ let(:random) { coerce_if_time_value(option, random_int_str) }
79
84
  let(:options) { { option.to_sym => random } }
80
85
 
81
- let(:token_response) { { access_token: {} } }
86
+ let(:token_response) { {} }
82
87
  let!(:request_token_stub) do
83
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
88
+ stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
84
89
  with do |request|
85
- request_body_includes(request, protocol, option, random)
90
+ request_body_includes(request, protocol, option, coerce_if_time_value(option, random, multiply: 1000))
86
91
  end.to_return(
87
92
  :status => 201,
88
93
  :body => serialize(token_response, protocol),
@@ -92,25 +97,25 @@ describe Ably::Auth do
92
97
 
93
98
  before { auth.request_token options }
94
99
 
95
- it 'overrides default and uses camelCase notation for all attributes' do
100
+ it "overrides default and uses camelCase notation for attributes" do
96
101
  expect(request_token_stub).to have_been_requested
97
102
  end
98
103
  end
99
104
  end
100
105
 
101
- context 'with :key_id & :key_secret options', :webmock do
102
- let(:key_id) { random_str }
106
+ context 'with :key option', :webmock do
107
+ let(:key_name) { "app.#{random_str}" }
103
108
  let(:key_secret) { random_str }
104
109
  let(:nonce) { random_str }
105
- let(:token_options) { { key_id: key_id, key_secret: key_secret, nonce: nonce, timestamp: Time.now } }
110
+ let(:token_options) { { key: "#{key_name}:#{key_secret}", nonce: nonce, timestamp: Time.now } }
106
111
  let(:token_request) { auth.create_token_request(token_options) }
107
112
  let(:mac) do
108
113
  hmac_for(token_request, key_secret)
109
114
  end
110
115
 
111
- let(:token_response) { { access_token: {} } }
116
+ let(:token_response) { {} }
112
117
  let!(:request_token_stub) do
113
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
118
+ stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
114
119
  with do |request|
115
120
  request_body_includes(request, protocol, 'mac', mac)
116
121
  end.to_return(
@@ -119,9 +124,38 @@ describe Ably::Auth do
119
124
  :headers => { 'Content-Type' => content_type })
120
125
  end
121
126
 
122
- let!(:token) { auth.request_token(token_options) }
127
+ let!(:token) { puts token_options; auth.request_token(token_options) }
123
128
 
124
- specify 'key_id is used in request and signing uses key_secret' do
129
+ specify 'key_name is used in request and signing uses key_secret' do
130
+ expect(request_token_stub).to have_been_requested
131
+ end
132
+ end
133
+
134
+ context 'with :key_name & :key_secret options', :webmock do
135
+ let(:key_name) { "app.#{random_str}" }
136
+ let(:key_secret) { random_str }
137
+ let(:nonce) { random_str }
138
+
139
+ let(:name_secret_token_options) { { key_name: key_name, key_secret: key_secret, nonce: nonce, timestamp: Time.now } }
140
+ let(:token_request) { auth.create_token_request(name_secret_token_options) }
141
+ let(:mac) do
142
+ hmac_for(token_request, key_secret)
143
+ end
144
+
145
+ let(:token_response) { {} }
146
+ let!(:request_token_stub) do
147
+ stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
148
+ with do |request|
149
+ request_body_includes(request, protocol, 'mac', mac)
150
+ end.to_return(
151
+ :status => 201,
152
+ :body => serialize(token_response, protocol),
153
+ :headers => { 'Content-Type' => content_type })
154
+ end
155
+
156
+ let!(:token) { auth.request_token(name_secret_token_options); }
157
+
158
+ specify 'key_name is used in request and signing uses key_secret' do
125
159
  expect(request_token_stub).to have_been_requested
126
160
  end
127
161
  end
@@ -146,8 +180,8 @@ describe Ably::Auth do
146
180
 
147
181
  context 'with :auth_url option', :webmock do
148
182
  let(:auth_url) { 'https://www.fictitious.com/get_token' }
149
- let(:auth_url_response) { { id: key_id } }
150
- let(:token_response) { { access_token: { } } }
183
+ let(:auth_url_response) { { keyName: key_name }.to_json }
184
+ let(:token_response) { {} }
151
185
  let(:query_params) { nil }
152
186
  let(:headers) { nil }
153
187
  let(:auth_method) { :get }
@@ -166,15 +200,16 @@ describe Ably::Auth do
166
200
  stub.with(:headers => headers) unless headers.nil?
167
201
  stub.to_return(
168
202
  :status => 201,
169
- :body => auth_url_response.to_json,
170
- :headers => { 'Content-Type' => 'application/json' }
203
+ :body => auth_url_response,
204
+ :headers => { 'Content-Type' => auth_url_content_type }
171
205
  )
172
206
  end
207
+ let(:auth_url_content_type) { 'application/json' }
173
208
 
174
209
  let!(:request_token_stub) do
175
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
210
+ stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
176
211
  with do |request|
177
- request_body_includes(request, protocol, 'id', key_id)
212
+ request_body_includes(request, protocol, 'key_name', key_name)
178
213
  end.to_return(
179
214
  :status => 201,
180
215
  :body => serialize(token_response, protocol),
@@ -191,7 +226,7 @@ describe Ably::Auth do
191
226
  end
192
227
 
193
228
  it 'returns a valid token generated from the token request' do
194
- expect(token).to be_a(Ably::Models::Token)
229
+ expect(token).to be_a(Ably::Models::TokenDetails)
195
230
  end
196
231
 
197
232
  context 'with :query_params' do
@@ -220,29 +255,45 @@ describe Ably::Auth do
220
255
  end
221
256
  end
222
257
 
223
- context 'when response from :auth_url is a token' do
224
- let(:token_id) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
225
- let(:issued_at) { Time.now }
258
+ context 'when response from :auth_url is a token details object' do
259
+ let(:token) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
260
+ let(:issued) { Time.now }
226
261
  let(:expires) { Time.now + 60}
227
262
  let(:capability) { {'foo'=>['publish']} }
263
+ let(:capability_str) { JSON.dump(capability) }
228
264
  let(:auth_url_response) do
229
265
  {
230
- 'id' => token_id,
231
- 'key' => 'J_0Tlg.NxCRig',
232
- 'issued_at' => issued_at.to_i,
233
- 'expires' => expires.to_i,
234
- 'capability'=> capability
235
- }
266
+ 'token' => token,
267
+ 'key_name' => 'J_0Tlg.NxCRig',
268
+ 'issued' => issued.to_i * 1000,
269
+ 'expires' => expires.to_i * 1000,
270
+ 'capability'=> capability_str
271
+ }.to_json
236
272
  end
237
273
 
238
- let!(:token) { auth.request_token(options) }
274
+ let!(:token_details) { auth.request_token(options) }
275
+
276
+ it 'returns TokenDetails created from the token JSON' do
277
+ expect(request_token_stub).to_not have_been_requested
278
+ expect(token_details).to be_a(Ably::Models::TokenDetails)
279
+ expect(token_details.token).to eql(token)
280
+ expect(token_details.expires).to be_within(1).of(expires)
281
+ expect(token_details.issued).to be_within(1).of(issued)
282
+ expect(token_details.capability).to eql(capability)
283
+ end
284
+ end
285
+
286
+ context 'when response from :auth_url is text/plain content type and a token string' do
287
+ let(:token) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
288
+ let(:auth_url_content_type) { 'text/plain' }
289
+ let(:auth_url_response) { token }
239
290
 
240
- it 'returns a Token created from the token JSON' do
291
+ let!(:token_details) { auth.request_token(options) }
292
+
293
+ it 'returns TokenDetails created from the token JSON' do
241
294
  expect(request_token_stub).to_not have_been_requested
242
- expect(token.id).to eql(token_id)
243
- expect(token.expires_at).to be_within(1).of(expires)
244
- expect(token.issued_at).to be_within(1).of(issued_at)
245
- expect(token.capability.to_json).to eql(capability.to_json)
295
+ expect(token_details).to be_a(Ably::Models::TokenDetails)
296
+ expect(token_details.token).to eql(token)
246
297
  end
247
298
  end
248
299
 
@@ -270,99 +321,135 @@ describe Ably::Auth do
270
321
  end
271
322
  end
272
323
 
273
- context 'with token_request_block that returns a token request' do
274
- let(:client_id) { random_str }
275
- let(:options) { { client_id: client_id } }
276
- let!(:request_token) do
277
- auth.request_token(options) do |block_options|
278
- @block_called = true
279
- @block_options = block_options
280
- auth.create_token_request(client_id: client_id)
324
+ context 'with a Proc for the :auth_callback option' do
325
+ context 'that returns a TokenRequest' do
326
+ let(:client_id) { random_str }
327
+ let(:options) { { client_id: client_id } }
328
+ let!(:request_token) do
329
+ auth.request_token(options.merge(auth_callback: Proc.new do |block_options|
330
+ @block_called = true
331
+ @block_options = block_options
332
+ auth.create_token_request(client_id: client_id)
333
+ end))
281
334
  end
282
- end
283
335
 
284
- it 'calls the block when authenticating to obtain the request token' do
285
- expect(@block_called).to eql(true)
286
- expect(@block_options).to include(options)
287
- end
336
+ it 'calls the Proc when authenticating to obtain the request token' do
337
+ expect(@block_called).to eql(true)
338
+ expect(@block_options).to include(options)
339
+ end
288
340
 
289
- it 'uses the token request from the block when requesting a new token' do
290
- expect(request_token.client_id).to eql(client_id)
341
+ it 'uses the token request returned from the callback when requesting a new token' do
342
+ expect(request_token.client_id).to eql(client_id)
343
+ end
291
344
  end
292
- end
293
345
 
294
- context 'with token_request_block that returns a token' do
295
- let(:client_id) { random_str }
296
- let(:options) { { client_id: client_id } }
297
- let(:token_id) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
298
- let(:issued_at) { Time.now }
299
- let(:expires) { Time.now + 60}
300
- let(:capability) { {'foo'=>['publish']} }
346
+ context 'that returns a TokenDetails JSON object' do
347
+ let(:client_id) { random_str }
348
+ let(:options) { { client_id: client_id } }
349
+ let(:token) { 'J_0Tlg.D7AVZkdOZW-PqNNGvCSp38' }
350
+ let(:issued) { Time.now }
351
+ let(:expires) { Time.now + 60}
352
+ let(:capability) { {'foo'=>['publish']} }
353
+ let(:capability_str) { JSON.dump(capability) }
354
+
355
+ let!(:token_details) do
356
+ auth.request_token(options.merge(auth_callback: Proc.new do |block_options|
357
+ @block_called = true
358
+ @block_options = block_options
359
+ {
360
+ 'token' => token,
361
+ 'keyName' => 'J_0Tlg.NxCRig',
362
+ 'clientId' => client_id,
363
+ 'issued' => issued.to_i * 1000,
364
+ 'expires' => expires.to_i * 1000,
365
+ 'capability'=> capability_str
366
+ }
367
+ end))
368
+ end
301
369
 
302
- let!(:request_token) do
303
- auth.request_token(options) do |block_options|
304
- @block_called = true
305
- @block_options = block_options
306
- {
307
- 'id' => token_id,
308
- 'key' => 'J_0Tlg.NxCRig',
309
- 'client_id' => client_id,
310
- 'issued_at' => issued_at.to_i,
311
- 'expires' => expires.to_i,
312
- 'capability'=> capability
313
- }
370
+ it 'calls the Proc when authenticating to obtain the request token' do
371
+ expect(@block_called).to eql(true)
372
+ expect(@block_options).to include(options)
373
+ end
374
+
375
+ it 'uses the token request returned from the callback when requesting a new token' do
376
+ expect(token_details).to be_a(Ably::Models::TokenDetails)
377
+ expect(token_details.token).to eql(token)
378
+ expect(token_details.client_id).to eql(client_id)
379
+ expect(token_details.expires).to be_within(1).of(expires)
380
+ expect(token_details.issued).to be_within(1).of(issued)
381
+ expect(token_details.capability).to eql(capability)
314
382
  end
315
383
  end
316
384
 
317
- it 'calls the block when authenticating to obtain the request token' do
318
- expect(@block_called).to eql(true)
319
- expect(@block_options).to include(options)
385
+ context 'that returns a TokenDetails object' do
386
+ let(:client_id) { random_str }
387
+
388
+ let!(:token_details) do
389
+ auth.request_token(auth_callback: Proc.new do |block_options|
390
+ auth.create_token_request({
391
+ client_id: client_id
392
+ })
393
+ end)
394
+ end
395
+
396
+ it 'uses the token request returned from the callback when requesting a new token' do
397
+ expect(token_details).to be_a(Ably::Models::TokenDetails)
398
+ expect(token_details.client_id).to eql(client_id)
399
+ end
320
400
  end
321
401
 
322
- it 'uses the token request from the block when requesting a new token' do
323
- expect(request_token).to be_a(Ably::Models::Token)
324
- expect(request_token.id).to eql(token_id)
325
- expect(request_token.client_id).to eql(client_id)
326
- expect(request_token.expires_at).to be_within(1).of(expires)
327
- expect(request_token.issued_at).to be_within(1).of(issued_at)
328
- expect(request_token.capability.to_json).to eql(capability.to_json)
402
+ context 'that returns a Token string' do
403
+ let(:second_client) { Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol) }
404
+ let(:token) { second_client.auth.request_token.token }
405
+
406
+ let!(:token_details) do
407
+ auth.request_token(auth_callback: Proc.new do |block_options|
408
+ token
409
+ end)
410
+ end
411
+
412
+ it 'uses the token request returned from the callback when requesting a new token' do
413
+ expect(token_details).to be_a(Ably::Models::TokenDetails)
414
+ expect(token_details.token).to eql(token)
415
+ end
329
416
  end
330
417
  end
331
418
 
332
419
  context 'persisted option', api_private: true do
333
420
  context 'when set to true', api_private: true do
334
421
  let(:options) { { persisted: true } }
335
- let(:token) { auth.request_token(options) }
422
+ let(:token_details) { auth.request_token(options) }
336
423
 
337
424
  it 'returns a token with a short token ID that is used to look up the token details' do
338
- expect(token.id.length).to be < 64
339
- expect(token.id).to match(/^#{app_id}\.A/)
425
+ expect(token_details.token.length).to be < 64
426
+ expect(token_details.token).to match(/^#{app_id}\.A/)
340
427
  end
341
428
  end
342
429
 
343
430
  context 'when omitted', api_private: true do
344
431
  let(:options) { { } }
345
- let(:token) { auth.request_token(options) }
432
+ let(:token_details) { auth.request_token(options) }
346
433
 
347
434
  it 'returns a literal token' do
348
- expect(token.id.length).to be > 64
435
+ expect(token_details.token.length).to be > 64
349
436
  end
350
437
  end
351
438
  end
352
439
 
353
440
  context 'with client_id' do
354
441
  let(:client_id) { random_str }
355
- let(:token) { auth.request_token(client_id: client_id) }
442
+ let(:token_details) { auth.request_token(client_id: client_id) }
356
443
 
357
444
  it 'returns a token with the client_id' do
358
- expect(token.client_id).to eql(client_id)
445
+ expect(token_details.client_id).to eql(client_id)
359
446
  end
360
447
  end
361
448
  end
362
449
 
363
450
  context 'before #authorise has been called' do
364
- it 'has no current_token' do
365
- expect(auth.current_token).to be_nil
451
+ it 'has no current_token_details' do
452
+ expect(auth.current_token_details).to be_nil
366
453
  end
367
454
  end
368
455
 
@@ -378,62 +465,62 @@ describe Ably::Auth do
378
465
  end
379
466
 
380
467
  it 'returns a valid token' do
381
- expect(auth.authorise).to be_a(Ably::Models::Token)
468
+ expect(auth.authorise).to be_a(Ably::Models::TokenDetails)
382
469
  end
383
470
 
384
471
  it 'issues a new token if option :force => true' do
385
- expect { auth.authorise(force: true) }.to change { auth.current_token }
472
+ expect { auth.authorise(force: true) }.to change { auth.current_token_details }
386
473
  end
387
474
  end
388
475
 
389
476
  context 'with previous authorisation' do
390
477
  before do
391
478
  auth.authorise
392
- expect(auth.current_token).to_not be_expired
479
+ expect(auth.current_token_details).to_not be_expired
393
480
  end
394
481
 
395
- it 'does not request a token if current_token has not expired' do
482
+ it 'does not request a token if current_token_details has not expired' do
396
483
  expect(auth).to_not receive(:request_token)
397
484
  auth.authorise
398
485
  end
399
486
 
400
487
  it 'requests a new token if token is expired' do
401
- allow(auth.current_token).to receive(:expired?).and_return(true)
488
+ allow(auth.current_token_details).to receive(:expired?).and_return(true)
402
489
  expect(auth).to receive(:request_token)
403
- expect { auth.authorise }.to change { auth.current_token }
490
+ expect { auth.authorise }.to change { auth.current_token_details }
404
491
  end
405
492
 
406
493
  it 'issues a new token if option :force => true' do
407
- expect { auth.authorise(force: true) }.to change { auth.current_token }
494
+ expect { auth.authorise(force: true) }.to change { auth.current_token_details }
408
495
  end
409
496
  end
410
497
 
411
- it 'updates the persisted auth options thare are then used for subsequent authorise requests' do
498
+ it 'updates the persisted auth options that are then used for subsequent authorise requests' do
412
499
  expect(auth.options[:ttl]).to_not eql(26)
413
500
  auth.authorise(ttl: 26)
414
501
  expect(auth.options[:ttl]).to eql(26)
415
502
  end
416
503
 
417
- context 'with token_request_block' do
504
+ context 'with a Proc for the :auth_callback option' do
418
505
  let(:client_id) { random_str }
419
506
  let!(:token) do
420
- auth.authorise do
507
+ auth.authorise(auth_callback: Proc.new do
421
508
  @block_called ||= 0
422
509
  @block_called += 1
423
510
  auth.create_token_request(client_id: client_id)
424
- end
511
+ end)
425
512
  end
426
513
 
427
- it 'calls the block' do
514
+ it 'calls the Proc' do
428
515
  expect(@block_called).to eql(1)
429
516
  end
430
517
 
431
- it 'uses the token request returned from the block when requesting a new token' do
518
+ it 'uses the token request returned from the callback when requesting a new token' do
432
519
  expect(token.client_id).to eql(client_id)
433
520
  end
434
521
 
435
522
  context 'for every subsequent #request_token' do
436
- context 'without a provided block' do
523
+ context 'without a :auth_callback Proc' do
437
524
  it 'calls the originally provided block' do
438
525
  auth.request_token
439
526
  expect(@block_called).to eql(2)
@@ -441,8 +528,8 @@ describe Ably::Auth do
441
528
  end
442
529
 
443
530
  context 'with a provided block' do
444
- it 'does not call the originally provided block and calls the new #request_token block' do
445
- auth.request_token { @request_block_called = true; auth.create_token_request }
531
+ it 'does not call the originally provided Proc and calls the new #request_token :auth_callback Proc' do
532
+ auth.request_token(auth_callback: Proc.new { @request_block_called = true; auth.create_token_request })
446
533
  expect(@block_called).to eql(1)
447
534
  expect(@request_block_called).to eql(true)
448
535
  end
@@ -454,19 +541,19 @@ describe Ably::Auth do
454
541
  describe '#create_token_request' do
455
542
  let(:ttl) { 60 * 60 }
456
543
  let(:capability) { { :foo => ["publish"] } }
457
- let(:options) { Hash.new }
458
- subject { auth.create_token_request(options) }
544
+ let(:token_request_options) { Hash.new }
545
+ subject { auth.create_token_request(token_request_options) }
459
546
 
460
- it 'uses the key ID from the client' do
461
- expect(subject['id']).to eql(key_id)
547
+ it 'uses the key name from the client' do
548
+ expect(subject['keyName']).to eql(key_name)
462
549
  end
463
550
 
464
551
  it 'uses the default TTL' do
465
- expect(subject['ttl']).to eql(Ably::Models::Token::DEFAULTS[:ttl])
552
+ expect(subject['ttl']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl) * 1000)
466
553
  end
467
554
 
468
555
  it 'uses the default capability' do
469
- expect(subject['capability']).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
556
+ expect(subject['capability']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:capability).to_json)
470
557
  end
471
558
 
472
559
  context 'the nonce' do
@@ -480,25 +567,25 @@ describe Ably::Auth do
480
567
  end
481
568
  end
482
569
 
483
- %w(ttl capability nonce timestamp client_id).each do |attribute|
570
+ %w(ttl nonce client_id).each do |attribute|
484
571
  context "with option :#{attribute}" do
485
- let(:option_value) { random_int_str(1_000_000_000) }
572
+ let(:option_value) { random_int_str(1_000_000_000).to_i }
486
573
  before do
487
- options[attribute.to_sym] = option_value
574
+ token_request_options[attribute.to_sym] = option_value
488
575
  end
489
576
  it "overrides default" do
490
- expect(subject[convert_to_mixed_case(attribute)].to_s).to eql(option_value.to_s)
577
+ expect(subject.public_send(attribute).to_s).to eql(option_value.to_s)
491
578
  end
492
579
  end
493
580
  end
494
581
 
495
582
  context 'with additional invalid attributes' do
496
- let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
583
+ let(:token_request_options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
497
584
  specify 'are ignored' do
498
- expect(subject.keys).to_not include(:is_not_used_by_token_request)
499
- expect(subject.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
500
- expect(subject.keys).to include('nonce')
501
- expect(subject['nonce']).to eql('valid')
585
+ expect(subject.hash.keys).to_not include(:is_not_used_by_token_request)
586
+ expect(subject.hash.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
587
+ expect(subject.hash.keys).to include(:nonce)
588
+ expect(subject.nonce).to eql('valid')
502
589
  end
503
590
  end
504
591
 
@@ -506,47 +593,52 @@ describe Ably::Auth do
506
593
  let(:client) { Ably::Rest::Client.new(auth_url: 'http://example.com', protocol: protocol) }
507
594
 
508
595
  it 'should raise an exception if key secret is missing' do
509
- expect { auth.create_token_request(key_id: 'id') }.to raise_error Ably::Exceptions::TokenRequestError
596
+ expect { auth.create_token_request(key_name: 'name') }.to raise_error Ably::Exceptions::TokenRequestError
510
597
  end
511
598
 
512
- it 'should raise an exception if key id is missing' do
599
+ it 'should raise an exception if key name is missing' do
513
600
  expect { auth.create_token_request(key_secret: 'secret') }.to raise_error Ably::Exceptions::TokenRequestError
514
601
  end
515
602
  end
516
603
 
517
604
  context 'with :query_time option' do
518
605
  let(:time) { Time.now - 30 }
519
- let(:options) { { query_time: true } }
606
+ let(:token_request_options) { { query_time: true } }
520
607
 
521
608
  it 'queries the server for the timestamp' do
522
609
  expect(client).to receive(:time).and_return(time)
523
- expect(subject['timestamp']).to eql(time.to_i)
610
+ expect(subject['timestamp']).to be_within(1).of(time.to_f * 1000)
524
611
  end
525
612
  end
526
613
 
527
614
  context 'with :timestamp option' do
528
615
  let(:token_request_time) { Time.now + 5 }
529
- let(:options) { { timestamp: token_request_time } }
616
+ let(:token_request_options) { { timestamp: token_request_time } }
530
617
 
531
618
  it 'uses the provided timestamp in the token request' do
532
- expect(subject['timestamp']).to eql(token_request_time.to_i)
619
+ expect(subject['timestamp']).to be_within(1).of(token_request_time.to_f * 1000)
533
620
  end
534
621
  end
535
622
 
536
623
  context 'signing' do
537
- let(:options) do
624
+ let(:token_request_options) do
538
625
  {
539
- id: random_str,
540
- ttl: random_str,
626
+ key_name: random_str,
627
+ ttl: random_int_str.to_i,
541
628
  capability: random_str,
542
629
  client_id: random_str,
543
- timestamp: random_int_str,
630
+ timestamp: random_int_str.to_i,
544
631
  nonce: random_str
545
632
  }
546
633
  end
547
634
 
635
+ # TokenRequest expects times in milliseconds, whereas create_token_request assumes Ruby default of seconds
636
+ let(:token_request_attributes) do
637
+ token_request_options.merge(timestamp: token_request_options[:timestamp] * 1000, ttl: token_request_options[:ttl] * 1000)
638
+ end
639
+
548
640
  it 'generates a valid HMAC' do
549
- hmac = hmac_for(options, key_secret)
641
+ hmac = hmac_for(Ably::Models::TokenRequest(token_request_attributes).hash, key_secret)
550
642
  expect(subject['mac']).to eql(hmac)
551
643
  end
552
644
  end
@@ -555,20 +647,20 @@ describe Ably::Auth do
555
647
  context 'using token authentication' do
556
648
  let(:capability) { { :foo => ["publish"] } }
557
649
 
558
- describe 'with :token_id option' do
650
+ describe 'with :token option' do
559
651
  let(:ttl) { 60 * 60 }
560
- let(:token) do
652
+ let(:token_details) do
561
653
  auth.request_token(
562
654
  ttl: ttl,
563
655
  capability: capability
564
656
  )
565
657
  end
566
- let(:token_id) { token.id }
658
+ let(:token) { token_details.token }
567
659
  let(:token_auth_client) do
568
- Ably::Rest::Client.new(token_id: token_id, environment: environment, protocol: protocol)
660
+ Ably::Rest::Client.new(token: token, environment: environment, protocol: protocol)
569
661
  end
570
662
 
571
- it 'authenticates successfully using the provided :token_id' do
663
+ it 'authenticates successfully using the provided :token' do
572
664
  expect(token_auth_client.channel('foo').publish('event', 'data')).to be_truthy
573
665
  end
574
666
 
@@ -598,23 +690,21 @@ describe Ably::Auth do
598
690
  let(:client) do
599
691
  Ably::Rest::Client.new(key: api_key, client_id: client_id, environment: environment, protocol: protocol)
600
692
  end
601
- let(:token_id) { 'unique-token-id' }
693
+ let(:token) { 'unique-token' }
602
694
  let(:token_response) do
603
695
  {
604
- access_token: {
605
- id: token_id
606
- }
696
+ token: token
607
697
  }.to_json
608
698
  end
609
699
 
610
700
  context 'and requests to the Ably server are mocked', :webmock do
611
701
  let!(:request_token_stub) do
612
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").
702
+ stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
613
703
  to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
614
704
  end
615
705
  let!(:publish_message_stub) do
616
706
  stub_request(:post, "#{client.endpoint}/channels/foo/publish").
617
- with(headers: { 'Authorization' => "Bearer #{encode64(token_id)}" }).
707
+ with(headers: { 'Authorization' => "Bearer #{encode64(token)}" }).
618
708
  to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
619
709
  end
620
710
 
@@ -625,7 +715,7 @@ describe Ably::Auth do
625
715
  end
626
716
 
627
717
  describe 'a token is created' do
628
- let(:token) { client.auth.current_token }
718
+ let(:token) { client.auth.current_token_details }
629
719
 
630
720
  it 'before a request is made' do
631
721
  expect(token).to be_nil
@@ -638,11 +728,11 @@ describe Ably::Auth do
638
728
  it 'with capability and TTL defaults' do
639
729
  client.channel('foo').publish('event', 'data')
640
730
 
641
- expect(token).to be_a(Ably::Models::Token)
642
- capability_with_str_key = Ably::Models::Token::DEFAULTS[:capability]
643
- capability = Hash[capability_with_str_key.keys.map(&:to_sym).zip(capability_with_str_key.values)]
644
- expect(token.capability).to eq(JSON.dump(capability))
645
- expect(token.expires_at.to_i).to be_within(2).of(Time.now.to_i + Ably::Models::Token::DEFAULTS[:ttl])
731
+ expect(token).to be_a(Ably::Models::TokenDetails)
732
+ capability_with_str_key = Ably::Auth::TOKEN_DEFAULTS.fetch(:capability)
733
+ capability = Hash[capability_with_str_key.keys.map(&:to_s).zip(capability_with_str_key.values)]
734
+ expect(token.capability).to eq(capability)
735
+ expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl))
646
736
  expect(token.client_id).to eq(client_id)
647
737
  end
648
738
  end
@@ -662,19 +752,5 @@ describe Ably::Auth do
662
752
  expect(auth).to be_using_basic_auth
663
753
  end
664
754
  end
665
-
666
- context 'when using legacy :api_key option and basic auth' do
667
- let(:client) do
668
- Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
669
- end
670
-
671
- specify '#using_token_auth? is false' do
672
- expect(auth).to_not be_using_token_auth
673
- end
674
-
675
- specify '#key attribute contains the key string' do
676
- expect(auth.key).to eql(api_key)
677
- end
678
- end
679
755
  end
680
756
  end