ably-rest 0.7.5 → 0.8.1

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 (76) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +2 -0
  3. data/README.md +41 -15
  4. data/SPEC.md +654 -518
  5. data/lib/submodules/ably-ruby/.gitignore +1 -0
  6. data/lib/submodules/ably-ruby/.gitmodules +3 -0
  7. data/lib/submodules/ably-ruby/README.md +54 -26
  8. data/lib/submodules/ably-ruby/SPEC.md +468 -322
  9. data/lib/submodules/ably-ruby/ably.gemspec +4 -2
  10. data/lib/submodules/ably-ruby/lib/ably/auth.rb +185 -131
  11. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +1 -1
  12. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +31 -44
  13. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +2 -2
  14. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +1 -2
  15. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +67 -24
  16. data/lib/submodules/ably-ruby/lib/ably/models/stats_types.rb +131 -0
  17. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +101 -0
  18. data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +108 -0
  19. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +3 -2
  20. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +1 -1
  21. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +2 -2
  22. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +3 -7
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +32 -5
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +1 -0
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +4 -8
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +5 -3
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +12 -1
  28. data/lib/submodules/ably-ruby/lib/ably/rest.rb +3 -7
  29. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +13 -10
  30. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +19 -20
  31. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +14 -12
  32. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  33. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +74 -23
  34. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +3 -3
  35. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +18 -18
  36. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +5 -5
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +12 -12
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +5 -5
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +56 -13
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +8 -8
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +1 -1
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +1 -1
  43. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +262 -158
  44. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +11 -9
  45. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +28 -21
  46. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +1 -1
  47. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +30 -27
  48. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +1 -1
  49. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +10 -10
  50. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +93 -56
  51. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +50 -45
  52. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  53. data/lib/submodules/ably-ruby/spec/rspec_config.rb +3 -2
  54. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +36 -28
  55. data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -0
  56. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +3 -3
  57. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +1 -1
  58. data/lib/submodules/ably-ruby/spec/support/test_app.rb +20 -33
  59. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +18 -1
  60. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +81 -72
  61. data/lib/submodules/ably-ruby/spec/unit/models/stats_spec.rb +289 -0
  62. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +111 -0
  63. data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +110 -0
  64. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +1 -1
  65. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
  66. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +1 -1
  67. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +1 -1
  68. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +8 -8
  69. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +1 -1
  70. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +1 -1
  71. metadata +9 -7
  72. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +0 -74
  73. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +0 -56
  74. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +0 -56
  75. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +0 -113
  76. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +0 -86
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Ably::VERSION
9
9
  spec.authors = ['Lewis Marshall', "Matthew O'Riordan"]
10
10
  spec.email = ['lewis@lmars.net', 'matt@ably.io']
11
- spec.description = %q{A Ruby client library for ably.io, the real-time messaging service}
12
- spec.summary = %q{A Ruby client library for ably.io, the real-time messaging service}
11
+ spec.description = %q{A Ruby client library for ably.io, the realtime messaging service}
12
+ spec.summary = %q{A Ruby client library for ably.io, the realtime messaging service}
13
13
  spec.homepage = 'http://github.com/ably/ably-ruby'
14
14
  spec.license = 'MIT'
15
15
 
@@ -33,4 +33,6 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency 'rspec-retry'
34
34
  spec.add_development_dependency 'yard'
35
35
  spec.add_development_dependency 'webmock'
36
+
37
+ spec.add_development_dependency 'coveralls'
36
38
  end
@@ -5,20 +5,20 @@ require 'securerandom'
5
5
  require 'ably/rest/middleware/external_exceptions'
6
6
 
7
7
  module Ably
8
- # Auth is responsible for authentication with {https://ably.io Ably} using basic or token authentication
8
+ # Auth is responsible for authentication with {https://www.ably.io Ably} using basic or token authentication
9
9
  #
10
- # Find out more about Ably authentication at: http://docs.ably.io/other/authentication/
10
+ # Find out more about Ably authentication at: https://www.ably.io/documentation/general/authentication/
11
11
  #
12
12
  # @!attribute [r] client_id
13
13
  # @return [String] The provided client ID, used for identifying this client for presence purposes
14
- # @!attribute [r] current_token
15
- # @return [Ably::Models::Token] Current {Ably::Models::Token} issued by this library or one of the provided callbacks used to authenticate requests
16
- # @!attribute [r] token_id
17
- # @return [String] Token ID provided to the {Ably::Client} constructor that is used to authenticate all requests
18
- # @!attribute [r] api_key
19
- # @return [String] Complete API key containing both the key ID and key secret, if present
20
- # @!attribute [r] key_id
21
- # @return [String] Key ID (public part of the API key), if present
14
+ # @!attribute [r] current_token_details
15
+ # @return [Ably::Models::TokenDetails] Current {Ably::Models::TokenDetails} issued by this library or one of the provided callbacks used to authenticate requests
16
+ # @!attribute [r] token
17
+ # @return [String] Token string provided to the {Ably::Client} constructor that is used to authenticate all requests
18
+ # @!attribute [r] key
19
+ # @return [String] Complete API key containing both the key name and key secret, if present
20
+ # @!attribute [r] key_name
21
+ # @return [String] Key name (public part of the API key), if present
22
22
  # @!attribute [r] key_secret
23
23
  # @return [String] Key secret (private secure part of the API key), if present
24
24
  # @!attribute [r] options
@@ -28,7 +28,13 @@ module Ably
28
28
  include Ably::Modules::Conversions
29
29
  include Ably::Modules::HttpHelpers
30
30
 
31
- attr_reader :options, :current_token
31
+ # Default capability Hash object and TTL in seconds for issued tokens
32
+ TOKEN_DEFAULTS = {
33
+ capability: { '*' => ['*'] },
34
+ ttl: 60 * 60 # 1 hour in seconds
35
+ }
36
+
37
+ attr_reader :options, :current_token_details
32
38
  alias_method :auth_options, :options
33
39
 
34
40
  # Creates an Auth object
@@ -36,31 +42,31 @@ module Ably
36
42
  # @param [Ably::Rest::Client] client {Ably::Rest::Client} this Auth object uses
37
43
  # @param [Hash] options (see Ably::Rest::Client#initialize)
38
44
  # @option (see Ably::Rest::Client#initialize)
39
- # @yield (see Ably::Rest::Client#initialize)
40
45
  #
41
- def initialize(client, options, &token_request_block)
46
+ def initialize(client, options)
47
+ ensure_valid_auth_attributes options
48
+
42
49
  auth_options = options.dup
43
50
 
44
51
  @client = client
45
52
  @options = auth_options
46
- @default_token_block = token_request_block if block_given?
47
53
 
48
54
  unless auth_options.kind_of?(Hash)
49
55
  raise ArgumentError, 'Expected auth_options to be a Hash'
50
56
  end
51
57
 
52
- if auth_options[:api_key] && (auth_options[:key_secret] || auth_options[:key_id])
53
- raise ArgumentError, 'api_key and key_id or key_secret are mutually exclusive. Provider either an api_key or key_id & key_secret'
58
+ if auth_options[:key] && (auth_options[:key_secret] || auth_options[:key_name])
59
+ raise ArgumentError, 'key and key_name or key_secret are mutually exclusive. Provider either a key or key_name & key_secret'
54
60
  end
55
61
 
56
- split_api_key_into_key_and_secret! auth_options if auth_options[:api_key]
62
+ split_api_key_into_key_and_secret! auth_options if auth_options[:key]
57
63
 
58
64
  if using_basic_auth? && !api_key_present?
59
- raise ArgumentError, 'api_key is missing. Either an API key, token, or token auth method must be provided'
65
+ raise ArgumentError, 'key is missing. Either an API key, token, or token auth method must be provided'
60
66
  end
61
67
 
62
68
  if has_client_id?
63
- raise ArgumentError, 'client_id cannot be provided without a complete API key. Key ID & Secret is needed to authenticate with Ably and obtain a token' unless api_key_present?
69
+ raise ArgumentError, 'client_id cannot be provided without a complete API key. Key name & Secret is needed to authenticate with Ably and obtain a token' unless api_key_present?
64
70
  ensure_utf_8 :client_id, client_id
65
71
  end
66
72
 
@@ -73,18 +79,14 @@ module Ably
73
79
  #
74
80
  # @param [Hash] options the options for the token request
75
81
  # @option options (see #request_token)
76
- # @option options [String] :api_key API key comprising the key ID and key secret in a single string
77
- # @option options [Boolean] :force obtains a new token even if the current token is valid
78
- #
79
- # @yield (see #request_token)
80
- # @yieldparam [Hash] options options passed to {#authorise} will be in turn sent to the block in this argument
81
- # @yieldreturn (see #request_token)
82
+ # @option options [String] :key API key comprising the key name and key secret in a single string
83
+ # @option options [Boolean] :force obtains a new token even if the current token is valid
82
84
  #
83
85
  # @return (see #request_token)
84
86
  #
85
87
  # @example
86
88
  # # will issue a simple token request using basic auth
87
- # client = Ably::Rest::Client.new(api_key: 'key.id:secret')
89
+ # client = Ably::Rest::Client.new(key: 'key.id:secret')
88
90
  # token = client.auth.authorise
89
91
  #
90
92
  # # will use token request from block to authorise if not already authorised
@@ -93,45 +95,42 @@ module Ably
93
95
  # token_request
94
96
  # end
95
97
  #
96
- def authorise(options = {}, &token_request_block)
97
- if !options[:force] && current_token
98
- return current_token unless current_token.expired?
98
+ def authorise(options = {})
99
+ ensure_valid_auth_attributes options
100
+
101
+ if current_token_details && !options[:force]
102
+ return current_token_details unless current_token_details.expired?
99
103
  end
100
104
 
101
105
  options = options.clone
102
- split_api_key_into_key_and_secret! options if options[:api_key]
103
-
104
- @options = @options.merge(options)
105
- @default_token_block = token_request_block if block_given?
106
+ split_api_key_into_key_and_secret! options if options[:key]
107
+ @options = @options.merge(options) # update default options
106
108
 
107
- @current_token = request_token(options, &token_request_block)
109
+ @current_token_details = request_token(options)
108
110
  end
109
111
 
110
- # Request a {Ably::Models::Token} which can be used to make authenticated token based requests
112
+ # Request a {Ably::Models::TokenDetails} which can be used to make authenticated token based requests
111
113
  #
112
114
  # @param [Hash] options the options for the token request
113
- # @option options [String] :key_id key ID for the designated application (defaults to client key_id)
114
- # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
115
- # @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
116
- # @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request.
117
- # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
118
- # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
119
- # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
120
- # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
121
- # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
122
- # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
123
- # @option options [Time] :timestamp the time of the of the request
124
- # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
115
+ # @option options [String] :key complete API key for the designated application
116
+ # @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
117
+ # @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request.
118
+ # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
119
+ # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
120
+ # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
121
+ # @option options [Proc] :auth_callback this Proc / block will be called with the {#auth_options Auth#auth_options Hash} as the first argument whenever a new token is required.
122
+ # The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
123
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::TokenDetails}. Limits may apply, see {https://www.ably.io/documentation/other/authentication}
124
+ # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
125
+ # @option options [Boolean] :query_time when true will query the {https://www.ably.io Ably} system for the current time instead of using the local time
126
+ # @option options [Time] :timestamp the time of the of the request
127
+ # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
125
128
  #
126
- # @yield [options] (optional) if a token request block is passed to this method, then this block will be called whenever a new token is required
127
- # @yieldparam [Hash] options options passed to {#request_token} will be in turn sent to the block in this argument
128
- # @yieldreturn [Hash] expects a valid token request object, see {#create_token_request}
129
- #
130
- # @return [Ably::Models::Token]
129
+ # @return [Ably::Models::TokenDetails]
131
130
  #
132
131
  # @example
133
132
  # # simple token request using basic auth
134
- # client = Ably::Rest::Client.new(api_key: 'key.id:secret')
133
+ # client = Ably::Rest::Client.new(key: 'key.id:secret')
135
134
  # token = client.auth.request_token
136
135
  #
137
136
  # # token request using auth block
@@ -141,96 +140,104 @@ module Ably
141
140
  # end
142
141
  #
143
142
  def request_token(options = {})
144
- token_options = self.auth_options.merge(options)
145
-
146
- auth_url = token_options.delete(:auth_url)
147
- token_request = if block_given?
148
- yield token_options
149
- elsif default_token_block
150
- default_token_block.call(token_options)
151
- elsif auth_url
143
+ ensure_valid_auth_attributes options
144
+
145
+ token_options = auth_options.merge(options)
146
+
147
+ token_request = if auth_callback = token_options.delete(:auth_callback)
148
+ auth_callback.call(token_options)
149
+ elsif auth_url = token_options.delete(:auth_url)
152
150
  token_request_from_auth_url(auth_url, token_options)
153
151
  else
154
152
  create_token_request(token_options)
155
153
  end
156
154
 
157
- token_request = IdiomaticRubyWrapper(token_request)
158
-
159
- if token_request.has_key?(:issued_at) && token_request.has_key?(:expires)
160
- Ably::Models::Token.new(token_request)
161
- else
162
- response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request.hash, send_auth_header: false, disable_automatic_reauthorise: true)
163
- body = IdiomaticRubyWrapper(response.body)
164
- Ably::Models::Token.new(body.fetch(:access_token))
155
+ case token_request
156
+ when Ably::Models::TokenDetails
157
+ return token_request
158
+ when Hash
159
+ return Ably::Models::TokenDetails.new(token_request) if IdiomaticRubyWrapper(token_request).has_key?(:issued)
160
+ when String
161
+ return Ably::Models::TokenDetails.new(token: token_request)
165
162
  end
163
+
164
+ token_request = Ably::Models::TokenRequest(token_request)
165
+
166
+ response = client.post("/keys/#{token_request.key_name}/requestToken",
167
+ token_request.hash, send_auth_header: false,
168
+ disable_automatic_reauthorise: true)
169
+
170
+ Ably::Models::TokenDetails.new(response.body)
166
171
  end
167
172
 
168
173
  # Creates and signs a token request that can then subsequently be used by any client to request a token
169
174
  #
170
175
  # @param [Hash] options the options for the token request
171
- # @option options [String] :key_id key ID for the designated application
172
- # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
176
+ # @option options [String] :key complete API key for the designated application
173
177
  # @option options [String] :client_id client ID identifying this connection to other clients
174
- # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
178
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::TokenDetails}. Limits may apply, see {https://www.ably.io/documentation/other/authentication}
175
179
  # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
176
- # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
180
+ # @option options [Boolean] :query_time when true will query the {https://www.ably.io Ably} system for the current time instead of using the local time
177
181
  # @option options [Time] :timestamp the time of the of the request
178
182
  # @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
179
- # @return [Hash]
183
+ #
184
+ # @return [Models::TokenRequest]
180
185
  #
181
186
  # @example
182
- # client.auth.create_request_token(id: 'asd.asd', ttl: 3600)
183
- # # => {
187
+ # client.auth.create_token_request(id: 'asd.asd', ttl: 3600)
188
+ # #<Ably::Models::TokenRequest:0x007fd5d919df78
189
+ # # @hash={
184
190
  # # :id=>"asds.adsa",
185
- # # :client_id=>nil,
186
- # # :ttl=>3600,
187
- # # :timestamp=>1410718527,
191
+ # # :clientId=>nil,
192
+ # # :ttl=>3600000,
193
+ # # :timestamp=>1428973674000,
188
194
  # # :capability=>"{\"*\":[\"*\"]}",
189
195
  # # :nonce=>"95e543b88299f6bae83df9b12fbd1ecd",
190
- # # :mac=>"881oZHeFo6oMim7N64y2vFHtSlpQ2gn/uE56a8gUxHw="
191
- # # }
196
+ # # :mac=>"881oZHeFo6oMim7....uE56a8gUxHw="
197
+ # # }
198
+ # #>>
192
199
  def create_token_request(options = {})
193
- token_attributes = %w(id client_id ttl timestamp capability nonce persisted)
200
+ ensure_valid_auth_attributes options
194
201
 
195
- token_options = options.clone
202
+ token_options = options.clone
196
203
 
197
- request_key_id = token_options.delete(:key_id) || key_id
204
+ split_api_key_into_key_and_secret! token_options if token_options[:key]
205
+ request_key_name = token_options.delete(:key_name) || key_name
198
206
  request_key_secret = token_options.delete(:key_secret) || key_secret
199
-
200
- raise Ably::Exceptions::TokenRequestError, 'Key ID and Key Secret are required to generate a new token request' unless request_key_id && request_key_secret
207
+ raise Ably::Exceptions::TokenRequestError, 'Key Name and Key Secret are required to generate a new token request' unless request_key_name && request_key_secret
201
208
 
202
209
  timestamp = if token_options[:query_time]
203
210
  client.time
204
211
  else
205
212
  token_options.delete(:timestamp) || Time.now
206
- end.to_i
213
+ end
214
+ timestamp = Time.at(timestamp) if timestamp.kind_of?(Integer)
207
215
 
208
216
  token_request = {
209
- id: request_key_id,
210
- clientId: client_id,
211
- ttl: Ably::Models::Token::DEFAULTS[:ttl],
212
- timestamp: timestamp,
213
- capability: Ably::Models::Token::DEFAULTS[:capability],
214
- nonce: SecureRandom.hex
215
- }.merge(token_options.select { |key, val| token_attributes.include?(key.to_s) })
216
-
217
- if token_request[:capability].is_a?(Hash)
218
- token_request[:capability] = token_request[:capability].to_json
219
- end
217
+ keyName: token_options[:key_name] || request_key_name,
218
+ clientId: token_options[:client_id] || client_id,
219
+ ttl: ((token_options[:ttl] || TOKEN_DEFAULTS.fetch(:ttl)) * 1000).to_i,
220
+ timestamp: (timestamp.to_f * 1000).round,
221
+ capability: token_options[:capability] || TOKEN_DEFAULTS.fetch(:capability),
222
+ nonce: token_options[:nonce] || SecureRandom.hex.force_encoding('UTF-8')
223
+ }
220
224
 
221
- ensure_utf_8 :nonce, token_request[:nonce], allow_nil: true
225
+ token_request[:capability] = JSON.dump(token_request[:capability]) if token_request[:capability].is_a?(Hash)
222
226
 
223
227
  token_request[:mac] = sign_params(token_request, request_key_secret)
224
228
 
225
- convert_to_mixed_case_hash(token_request)
229
+ # Undocumented feature to request a persisted token
230
+ token_request[:persisted] = options[:persisted] if options[:persisted]
231
+
232
+ Models::TokenRequest.new(token_request)
226
233
  end
227
234
 
228
- def api_key
229
- "#{key_id}:#{key_secret}" if api_key_present?
235
+ def key
236
+ "#{key_name}:#{key_secret}" if api_key_present?
230
237
  end
231
238
 
232
- def key_id
233
- options[:key_id]
239
+ def key_name
240
+ options[:key_name]
234
241
  end
235
242
 
236
243
  def key_secret
@@ -245,15 +252,21 @@ module Ably
245
252
  # True when Token Auth is being used to authenticate with Ably
246
253
  def using_token_auth?
247
254
  return options[:use_token_auth] if options.has_key?(:use_token_auth)
248
- token_id || current_token || has_client_id? || token_creatable_externally?
255
+ token || current_token_details || has_client_id? || token_creatable_externally?
249
256
  end
250
257
 
251
258
  def client_id
252
259
  options[:client_id]
253
260
  end
254
261
 
255
- def token_id
256
- options[:token_id]
262
+ def token
263
+ token_object = options[:token] || options[:token_details]
264
+
265
+ if token_object.kind_of?(Ably::Models::TokenDetails)
266
+ token_object.token
267
+ else
268
+ token_object
269
+ end
257
270
  end
258
271
 
259
272
  # Auth header string used in HTTP requests to Ably
@@ -281,13 +294,13 @@ module Ably
281
294
  # True if prerequisites for creating a new token request are present
282
295
  #
283
296
  # One of the following criterion must be met:
284
- # * Valid key id and secret and token_id option not provided as token options cannot be determined
297
+ # * Valid API key and token option not provided as token options cannot be determined
285
298
  # * Authentication callback for new token requests
286
299
  # * Authentication URL for new token requests
287
300
  #
288
301
  # @return [Boolean]
289
302
  def token_renewable?
290
- token_creatable_externally? || (api_key_present? && !token_id)
303
+ token_creatable_externally? || (api_key_present? && !token)
291
304
  end
292
305
 
293
306
  # Returns false when attempting to send an API Key over a non-secure connection
@@ -299,7 +312,45 @@ module Ably
299
312
  end
300
313
 
301
314
  private
302
- attr_reader :client, :default_token_block
315
+ attr_reader :client
316
+
317
+ def ensure_valid_auth_attributes(attributes)
318
+ if attributes[:timestamp]
319
+ unless attributes[:timestamp].kind_of?(Time) || attributes[:timestamp].kind_of?(Numeric)
320
+ raise ArgumentError, ':timestamp must be a Time or positive Integer value of seconds since epoch'
321
+ end
322
+ end
323
+
324
+ if attributes[:ttl]
325
+ unless attributes[:ttl].kind_of?(Numeric) && attributes[:ttl].to_f > 0
326
+ raise ArgumentError, ':ttl must be a positive Numeric value representing time to live in seconds'
327
+ end
328
+ end
329
+
330
+ if attributes[:auth_headers]
331
+ unless attributes[:auth_headers].kind_of?(Hash)
332
+ raise ArgumentError, ':auth_headers must be a valid Hash'
333
+ end
334
+ end
335
+
336
+ if attributes[:auth_params]
337
+ unless attributes[:auth_params].kind_of?(Hash)
338
+ raise ArgumentError, ':auth_params must be a valid Hash'
339
+ end
340
+ end
341
+
342
+ if attributes[:auth_method]
343
+ unless %(get post).include?(attributes[:auth_method].to_s)
344
+ raise ArgumentError, ':auth_method must be either :get or :post'
345
+ end
346
+ end
347
+
348
+ if attributes[:auth_callback]
349
+ unless attributes[:auth_callback].respond_to?(:call)
350
+ raise ArgumentError, ':auth_callback must be a Proc'
351
+ end
352
+ end
353
+ end
303
354
 
304
355
  def ensure_api_key_sent_over_secure_connection
305
356
  raise Ably::Exceptions::InsecureRequestError, 'Cannot use Basic Auth over non-TLS connections' unless authentication_security_requirements_met?
@@ -308,44 +359,45 @@ module Ably
308
359
  # Basic Auth HTTP Authorization header value
309
360
  def basic_auth_header
310
361
  ensure_api_key_sent_over_secure_connection
311
- "Basic #{encode64("#{api_key}")}"
362
+ "Basic #{encode64("#{key}")}"
312
363
  end
313
364
 
314
365
  def split_api_key_into_key_and_secret!(options)
315
- api_key_parts = options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
316
- raise ArgumentError, 'api_key is invalid' unless api_key_parts
366
+ api_key_parts = options[:key].to_s.match(/(?<name>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
367
+ raise ArgumentError, 'key is invalid' unless api_key_parts
317
368
 
318
- options[:key_id] = api_key_parts[:id].encode(Encoding::UTF_8)
369
+ options[:key_name] = api_key_parts[:name].encode(Encoding::UTF_8)
319
370
  options[:key_secret] = api_key_parts[:secret].encode(Encoding::UTF_8)
371
+
372
+ options.delete :key
320
373
  end
321
374
 
322
- def token_auth_id
323
- if token_id
324
- token_id
375
+ # Returns the current token if it exists or authorises and retrieves a token
376
+ def token_auth_string
377
+ if token
378
+ token
325
379
  else
326
- authorise.id
380
+ authorise.token
327
381
  end
328
382
  end
329
383
 
330
384
  # Token Auth HTTP Authorization header value
331
385
  def token_auth_header
332
- "Bearer #{encode64(token_auth_id)}"
386
+ "Bearer #{encode64(token_auth_string)}"
333
387
  end
334
388
 
335
389
  # Basic Auth params to authenticate the Realtime connection
336
390
  def basic_auth_params
337
391
  ensure_api_key_sent_over_secure_connection
338
- # TODO: Change to key_secret when API is updated
339
392
  {
340
- key_id: key_id,
341
- key_value: key_secret
393
+ key: key
342
394
  }
343
395
  end
344
396
 
345
397
  # Token Auth params to authenticate the Realtime connection
346
398
  def token_auth_params
347
399
  {
348
- access_token: token_auth_id
400
+ access_token: token_auth_string
349
401
  }
350
402
  end
351
403
 
@@ -354,13 +406,15 @@ module Ably
354
406
  # @return [Hash]
355
407
  def sign_params(params, secret)
356
408
  text = params.values_at(
357
- :id,
409
+ :keyName,
358
410
  :ttl,
359
411
  :capability,
360
- :client_id,
412
+ :clientId,
361
413
  :timestamp,
362
414
  :nonce
363
- ).map { |t| "#{t}\n" }.join("")
415
+ ).map do |val|
416
+ "#{val}\n"
417
+ end.join('')
364
418
 
365
419
  encode64(
366
420
  OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
@@ -381,7 +435,7 @@ module Ably
381
435
  request.headers = options[:auth_headers] || {}
382
436
  end
383
437
 
384
- unless response.body.kind_of?(Hash)
438
+ if !response.body.kind_of?(Hash) && response.headers['Content-Type'] != 'text/plain'
385
439
  raise Ably::Exceptions::InvalidResponseBody,
386
440
  "Content Type #{response.headers['Content-Type']} is not supported by this client library"
387
441
  end
@@ -423,8 +477,8 @@ module Ably
423
477
  end
424
478
  end
425
479
 
426
- def token_callback_present?
427
- !!default_token_block
480
+ def auth_callback_present?
481
+ !!options[:auth_callback]
428
482
  end
429
483
 
430
484
  def token_url_present?
@@ -432,7 +486,7 @@ module Ably
432
486
  end
433
487
 
434
488
  def token_creatable_externally?
435
- token_callback_present? || token_url_present?
489
+ auth_callback_present? || token_url_present?
436
490
  end
437
491
 
438
492
  def has_client_id?
@@ -440,7 +494,7 @@ module Ably
440
494
  end
441
495
 
442
496
  def api_key_present?
443
- key_id && key_secret
497
+ key_name && key_secret
444
498
  end
445
499
  end
446
500
  end