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
@@ -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
|
12
|
-
spec.summary = %q{A Ruby client library for ably.io, the
|
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:
|
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]
|
15
|
-
# @return [Ably::Models::
|
16
|
-
# @!attribute [r]
|
17
|
-
# @return [String] Token
|
18
|
-
# @!attribute [r]
|
19
|
-
# @return [String] Complete API key containing both the key
|
20
|
-
# @!attribute [r]
|
21
|
-
# @return [String] Key
|
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
|
-
|
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
|
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[:
|
53
|
-
raise ArgumentError, '
|
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[:
|
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, '
|
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
|
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] :
|
77
|
-
# @option options [Boolean] :force
|
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(
|
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 = {}
|
97
|
-
|
98
|
-
|
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[:
|
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
|
-
@
|
109
|
+
@current_token_details = request_token(options)
|
108
110
|
end
|
109
111
|
|
110
|
-
# Request a {Ably::Models::
|
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] :
|
114
|
-
# @option options [String] :
|
115
|
-
# @option options [String] :
|
116
|
-
# @option options [
|
117
|
-
# @option options [Hash] :
|
118
|
-
# @option options [
|
119
|
-
# @option options [
|
120
|
-
#
|
121
|
-
# @option options [
|
122
|
-
# @option options [
|
123
|
-
# @option options [
|
124
|
-
# @option options [
|
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
|
-
# @
|
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(
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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] :
|
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::
|
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
|
-
#
|
183
|
+
#
|
184
|
+
# @return [Models::TokenRequest]
|
180
185
|
#
|
181
186
|
# @example
|
182
|
-
# client.auth.
|
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
|
-
# # :
|
186
|
-
# # :ttl=>
|
187
|
-
# # :timestamp=>
|
191
|
+
# # :clientId=>nil,
|
192
|
+
# # :ttl=>3600000,
|
193
|
+
# # :timestamp=>1428973674000,
|
188
194
|
# # :capability=>"{\"*\":[\"*\"]}",
|
189
195
|
# # :nonce=>"95e543b88299f6bae83df9b12fbd1ecd",
|
190
|
-
# # :mac=>"
|
191
|
-
# #
|
196
|
+
# # :mac=>"881oZHeFo6oMim7....uE56a8gUxHw="
|
197
|
+
# # }
|
198
|
+
# #>>
|
192
199
|
def create_token_request(options = {})
|
193
|
-
|
200
|
+
ensure_valid_auth_attributes options
|
194
201
|
|
195
|
-
token_options
|
202
|
+
token_options = options.clone
|
196
203
|
|
197
|
-
|
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
|
213
|
+
end
|
214
|
+
timestamp = Time.at(timestamp) if timestamp.kind_of?(Integer)
|
207
215
|
|
208
216
|
token_request = {
|
209
|
-
|
210
|
-
clientId: client_id,
|
211
|
-
ttl:
|
212
|
-
timestamp: timestamp,
|
213
|
-
capability:
|
214
|
-
nonce: SecureRandom.hex
|
215
|
-
}
|
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
|
-
|
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
|
-
|
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
|
229
|
-
"#{
|
235
|
+
def key
|
236
|
+
"#{key_name}:#{key_secret}" if api_key_present?
|
230
237
|
end
|
231
238
|
|
232
|
-
def
|
233
|
-
options[:
|
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
|
-
|
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
|
256
|
-
options[:
|
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
|
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? && !
|
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
|
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("#{
|
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[:
|
316
|
-
raise ArgumentError, '
|
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[:
|
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
|
-
|
323
|
-
|
324
|
-
|
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.
|
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(
|
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
|
-
|
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:
|
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
|
-
:
|
409
|
+
:keyName,
|
358
410
|
:ttl,
|
359
411
|
:capability,
|
360
|
-
:
|
412
|
+
:clientId,
|
361
413
|
:timestamp,
|
362
414
|
:nonce
|
363
|
-
).map
|
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
|
-
|
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
|
427
|
-
!!
|
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
|
-
|
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
|
-
|
497
|
+
key_name && key_secret
|
444
498
|
end
|
445
499
|
end
|
446
500
|
end
|