ably-pubsub-core 0.0.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ably/agent.rb +8 -0
- data/lib/ably/auth.rb +791 -0
- data/lib/ably/exceptions.rb +169 -0
- data/lib/ably/logger.rb +119 -0
- data/lib/ably/models/auth_details.rb +45 -0
- data/lib/ably/models/channel_details.rb +69 -0
- data/lib/ably/models/channel_metrics.rb +107 -0
- data/lib/ably/models/channel_occupancy.rb +50 -0
- data/lib/ably/models/channel_options.rb +120 -0
- data/lib/ably/models/channel_state_change.rb +84 -0
- data/lib/ably/models/channel_status.rb +63 -0
- data/lib/ably/models/cipher_params.rb +137 -0
- data/lib/ably/models/connection_details.rb +149 -0
- data/lib/ably/models/connection_state_change.rb +83 -0
- data/lib/ably/models/delta_extras.rb +28 -0
- data/lib/ably/models/device_details.rb +126 -0
- data/lib/ably/models/device_push_details.rb +94 -0
- data/lib/ably/models/error_info.rb +104 -0
- data/lib/ably/models/http_paginated_response.rb +112 -0
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
- data/lib/ably/models/message.rb +223 -0
- data/lib/ably/models/message_encoders/base.rb +135 -0
- data/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/ably/models/message_encoders/cipher.rb +84 -0
- data/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_result.rb +201 -0
- data/lib/ably/models/presence_message.rb +244 -0
- data/lib/ably/models/protocol_message.rb +288 -0
- data/lib/ably/models/push_channel_subscription.rb +114 -0
- data/lib/ably/models/stats.rb +242 -0
- data/lib/ably/models/stats_types.rb +107 -0
- data/lib/ably/models/token_details.rb +148 -0
- data/lib/ably/models/token_request.rb +180 -0
- data/lib/ably/modules/ably.rb +26 -0
- data/lib/ably/modules/async_wrapper.rb +69 -0
- data/lib/ably/modules/channels_collection.rb +105 -0
- data/lib/ably/modules/conversions.rb +153 -0
- data/lib/ably/modules/encodeable.rb +111 -0
- data/lib/ably/modules/enum.rb +230 -0
- data/lib/ably/modules/event_emitter.rb +200 -0
- data/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/ably/modules/message_emitter.rb +78 -0
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +84 -0
- data/lib/ably/modules/safe_deferrable.rb +83 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +181 -0
- data/lib/ably/modules/state_machine.rb +59 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +111 -0
- data/lib/ably/realtime/auth.rb +283 -0
- data/lib/ably/realtime/channel/channel_manager.rb +272 -0
- data/lib/ably/realtime/channel/channel_properties.rb +40 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
- data/lib/ably/realtime/channel/publisher.rb +82 -0
- data/lib/ably/realtime/channel/push_channel.rb +69 -0
- data/lib/ably/realtime/channel.rb +440 -0
- data/lib/ably/realtime/channels.rb +71 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
- data/lib/ably/realtime/client.rb +363 -0
- data/lib/ably/realtime/connection/connection_manager.rb +594 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
- data/lib/ably/realtime/connection.rb +738 -0
- data/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/ably/realtime/presence/members_map.rb +395 -0
- data/lib/ably/realtime/presence/presence_manager.rb +78 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
- data/lib/ably/realtime/presence.rb +502 -0
- data/lib/ably/realtime/push/admin.rb +75 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/realtime/push.rb +25 -0
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/realtime.rb +60 -0
- data/lib/ably/rest/channel/push_channel.rb +59 -0
- data/lib/ably/rest/channel.rb +194 -0
- data/lib/ably/rest/channels.rb +47 -0
- data/lib/ably/rest/client.rb +727 -0
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +53 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
- data/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
- data/lib/ably/rest/presence.rb +113 -0
- data/lib/ably/rest/push/admin.rb +58 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +114 -0
- data/lib/ably/rest/push.rb +25 -0
- data/lib/ably/rest.rb +40 -0
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +134 -0
- data/lib/ably/util/pub_sub.rb +45 -0
- data/lib/ably/util/safe_deferrable.rb +44 -0
- data/lib/ably/version.rb +7 -0
- data/lib/ably.rb +16 -0
- metadata +497 -0
data/lib/ably/auth.rb
ADDED
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'faraday'
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
require 'ably/rest/middleware/external_exceptions'
|
|
6
|
+
|
|
7
|
+
module Ably
|
|
8
|
+
# Creates Ably {Ably::Models::TokenRequest} objects and obtains Ably Tokens from Ably to subsequently issue to less trusted clients.
|
|
9
|
+
#
|
|
10
|
+
class Auth
|
|
11
|
+
include Ably::Modules::Conversions
|
|
12
|
+
include Ably::Modules::HttpHelpers
|
|
13
|
+
|
|
14
|
+
# Default capability Hash object and TTL in seconds for issued tokens
|
|
15
|
+
TOKEN_DEFAULTS = {
|
|
16
|
+
renew_token_buffer: 10 # buffer to allow a token to be reissued before the token is considered expired (Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER)
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
API_KEY_REGEX = /^[\w-]{2,}\.[\w-]{2,}:[\w-]{2,}$/
|
|
20
|
+
|
|
21
|
+
# Supported AuthOption keys, see https://www.ably.com/docs/realtime/types#auth-options
|
|
22
|
+
# TODO: Review client_id usage embedded incorrectly within AuthOptions.
|
|
23
|
+
# This is legacy code to configure a client with a client_id from the ClientOptions
|
|
24
|
+
# TODO: Review inclusion of use_token_auth, ttl, token_params in auth options
|
|
25
|
+
AUTH_OPTIONS_KEYS = %w(
|
|
26
|
+
auth_callback
|
|
27
|
+
auth_url
|
|
28
|
+
auth_method
|
|
29
|
+
auth_headers
|
|
30
|
+
auth_params
|
|
31
|
+
client_id
|
|
32
|
+
key
|
|
33
|
+
key_name
|
|
34
|
+
key_secret
|
|
35
|
+
query_time
|
|
36
|
+
token
|
|
37
|
+
token_details
|
|
38
|
+
token_params
|
|
39
|
+
ttl
|
|
40
|
+
use_token_auth
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
attr_reader :options, :token_params, :current_token_details
|
|
44
|
+
alias_method :auth_options, :options
|
|
45
|
+
|
|
46
|
+
# Creates an Auth object
|
|
47
|
+
#
|
|
48
|
+
# @param [Ably::Rest::Client] client {Ably::Rest::Client} this Auth object uses
|
|
49
|
+
# @param [Hash] token_params the token params used as a default for future token requests
|
|
50
|
+
# @param [Hash] auth_options the authentication options used as a default future token requests
|
|
51
|
+
# @option (see #request_token)
|
|
52
|
+
#
|
|
53
|
+
def initialize(client, token_params, auth_options)
|
|
54
|
+
unless auth_options.kind_of?(Hash)
|
|
55
|
+
raise ArgumentError, 'Expected auth_options to be a Hash'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
unless token_params.kind_of?(Hash)
|
|
59
|
+
raise ArgumentError, 'Expected token_params to be a Hash'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Ensure instance variables are defined
|
|
63
|
+
@client_id = nil
|
|
64
|
+
@client_id_validated = nil
|
|
65
|
+
|
|
66
|
+
ensure_valid_auth_attributes auth_options
|
|
67
|
+
|
|
68
|
+
@client = client
|
|
69
|
+
@options = auth_options.dup
|
|
70
|
+
@token_params = token_params.dup
|
|
71
|
+
@token_option = options[:token] || options[:token_details]
|
|
72
|
+
|
|
73
|
+
if options[:key] && (options[:key_secret] || options[:key_name])
|
|
74
|
+
raise ArgumentError, 'key and key_name or key_secret are mutually exclusive. Provider either a key or key_name & key_secret'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
split_api_key_into_key_and_secret! options if options[:key]
|
|
78
|
+
store_and_delete_basic_auth_key_from_options! options
|
|
79
|
+
|
|
80
|
+
if using_basic_auth? && !api_key_present?
|
|
81
|
+
raise ArgumentError, 'key is missing. Either an API key, token, or token auth method must be provided'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if options[:client_id] == '*'
|
|
85
|
+
raise ArgumentError, 'A client cannot be configured with a wildcard client_id, only a token can have a wildcard client_id privilege'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if has_client_id? && !token_creatable_externally? && !token_option
|
|
89
|
+
@client_id = ensure_utf_8(:client_id, client_id) if client_id
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# If a token details object or token string is provided in the initializer
|
|
93
|
+
# then the client can be authorized immediately using this token
|
|
94
|
+
if token_option
|
|
95
|
+
token_details = convert_to_token_details(token_option)
|
|
96
|
+
if token_details
|
|
97
|
+
begin
|
|
98
|
+
token_details = authorize_with_token(token_details)
|
|
99
|
+
logger.debug { "Auth: new token passed in to the initializer: #{token_details}" }
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
logger.error { "Auth: Implicit authorization using the provided token failed: #{e}" }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@options.freeze
|
|
107
|
+
@token_params.freeze
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Instructs the library to get a new token immediately. When using the realtime client, it upgrades the current
|
|
111
|
+
# realtime connection to use the new token, or if not connected, initiates a connection to Ably, once the new token
|
|
112
|
+
# has been obtained. Also stores any `token_params` and `auth_options` passed
|
|
113
|
+
# in as the new defaults, to be used for all subsequent implicit or explicit token requests. Any `token_params`
|
|
114
|
+
# and `auth_options` objects passed in entirely replace, as opposed to being merged with, the current client library saved values.
|
|
115
|
+
#
|
|
116
|
+
# @spec RSA10
|
|
117
|
+
#
|
|
118
|
+
# @param [Hash, nil] token_params the token params used for future token requests. When nil, previously configured token params are used
|
|
119
|
+
# @param [Hash, nil] auth_options the authentication options used for future token requests. When nil, previously configure authentication options are used
|
|
120
|
+
# @option (see #request_token)
|
|
121
|
+
#
|
|
122
|
+
# @return (see #create_token_request)
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
# # will issue a simple token request using basic auth
|
|
126
|
+
# client = Ably::Rest::Client.new(key: 'key.id:secret')
|
|
127
|
+
# token_details = client.auth.authorize
|
|
128
|
+
#
|
|
129
|
+
# # will use token request from block to authorize if not already authorized
|
|
130
|
+
# token_details = client.auth.authorize {}, auth_callback: lambda do |token_parmas|
|
|
131
|
+
# # create token_request object
|
|
132
|
+
# token_request
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
def authorize(token_params = nil, auth_options = nil)
|
|
136
|
+
if auth_options.nil?
|
|
137
|
+
auth_options = options # Use default options
|
|
138
|
+
|
|
139
|
+
if options.has_key?(:query_time)
|
|
140
|
+
@options = options.dup
|
|
141
|
+
# Query the server time only happens once
|
|
142
|
+
# the options remain in auth_options though so they are passed to request_token
|
|
143
|
+
@options.delete(:query_time)
|
|
144
|
+
@options.freeze
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
ensure_valid_auth_attributes auth_options
|
|
148
|
+
|
|
149
|
+
auth_options = auth_options.dup
|
|
150
|
+
|
|
151
|
+
if auth_options[:token_params]
|
|
152
|
+
token_params = auth_options.delete(:token_params).merge(token_params || {})
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# If basic credentials are provided then overwrite existing options
|
|
156
|
+
# otherwise we need to retain the existing credentials in the auth options
|
|
157
|
+
split_api_key_into_key_and_secret! auth_options if auth_options[:key]
|
|
158
|
+
if auth_options[:key_name] && auth_options[:key_secret]
|
|
159
|
+
store_and_delete_basic_auth_key_from_options! auth_options
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
@options = auth_options.dup
|
|
163
|
+
|
|
164
|
+
# Query the server time only happens once
|
|
165
|
+
# the options remain in auth_options though so they are passed to request_token
|
|
166
|
+
@options.delete(:query_time)
|
|
167
|
+
|
|
168
|
+
@options.freeze
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Unless provided, defaults are used
|
|
172
|
+
unless token_params.nil?
|
|
173
|
+
@token_params = token_params.dup
|
|
174
|
+
# Timestamp is only valid for this request
|
|
175
|
+
@token_params.delete(:timestamp)
|
|
176
|
+
@token_params.freeze
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
authorize_with_token(request_token(token_params || @token_params, auth_options)).tap do |new_token_details|
|
|
180
|
+
logger.debug { "Auth: new token following authorisation: #{new_token_details}" }
|
|
181
|
+
|
|
182
|
+
# If authorize the realtime library required auth, then yield the token in a block
|
|
183
|
+
if block_given?
|
|
184
|
+
yield new_token_details
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @deprecated Use {#authorize} instead
|
|
190
|
+
def authorise(*args, &block)
|
|
191
|
+
logger.warn { "Auth#authorise is deprecated and will be removed in 1.0. Please use Auth#authorize instead" }
|
|
192
|
+
authorize(*args, &block)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Calls the requestToken REST API endpoint to obtain an Ably Token according to the specified `token_params` and `auth_options`.
|
|
196
|
+
# Both `token_params` and `auth_options` are optional. When omitted or null, the default token parameters and authentication
|
|
197
|
+
# options for the client library are used, as specified in the `client_options` when the client library was instantiated,
|
|
198
|
+
# or later updated with an explicit authorize request. Values passed in are used instead of, rather than being merged with, the default values.
|
|
199
|
+
# To understand why an Ably {Ably::Models::TokenRequest} may be issued to clients in favor of a token, see Token Authentication explained.
|
|
200
|
+
#
|
|
201
|
+
# @spec RSA8e
|
|
202
|
+
#
|
|
203
|
+
# @param [Hash] auth_options (see #create_token_request)
|
|
204
|
+
# @option auth_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
|
|
205
|
+
# @option auth_options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the +auth_url+
|
|
206
|
+
# @option auth_options [Hash] :auth_params a set of application-specific query params to be added to any request made to the +auth_url+
|
|
207
|
+
# @option auth_options [Symbol] :auth_method (:get) HTTP method to use with +auth_url+, must be either +:get+ or +:post+
|
|
208
|
+
# @option auth_options [Proc] :auth_callback when provided, the Proc will be called with the token params hash as the first argument, whenever a new token is required.
|
|
209
|
+
# The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
|
|
210
|
+
# @param [Hash] token_params (see #create_token_request)
|
|
211
|
+
# @option (see #create_token_request)
|
|
212
|
+
#
|
|
213
|
+
# @return [Ably::Models::TokenDetails] A {Ably::Models::TokenDetails} object. RSA16
|
|
214
|
+
#
|
|
215
|
+
# @example
|
|
216
|
+
# # simple token request using basic auth
|
|
217
|
+
# client = Ably::Rest::Client.new(key: 'key.id:secret')
|
|
218
|
+
# token_details = client.auth.request_token
|
|
219
|
+
#
|
|
220
|
+
# # token request with token params
|
|
221
|
+
# client.auth.request_token ttl: 1.hour
|
|
222
|
+
#
|
|
223
|
+
# # token request using auth block
|
|
224
|
+
# token_details = client.auth.request_token {}, auth_callback: lambda do |token_params|
|
|
225
|
+
# # create token_request object
|
|
226
|
+
# token_request
|
|
227
|
+
# end
|
|
228
|
+
#
|
|
229
|
+
def request_token(token_params = {}, auth_options = {})
|
|
230
|
+
ensure_valid_auth_attributes auth_options
|
|
231
|
+
|
|
232
|
+
# Token param precedence (lowest to highest):
|
|
233
|
+
# Auth default => client_id => auth_options[:token_params] arg => token_params arg
|
|
234
|
+
token_params = self.token_params.merge(
|
|
235
|
+
(client_id ? { client_id: client_id } : {}).
|
|
236
|
+
merge(auth_options[:token_params] || {}).
|
|
237
|
+
merge(token_params)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
auth_options = self.options.merge(auth_options)
|
|
241
|
+
|
|
242
|
+
token_request = if auth_callback = auth_options.delete(:auth_callback)
|
|
243
|
+
begin
|
|
244
|
+
Timeout::timeout(client.auth_request_timeout) do
|
|
245
|
+
auth_callback.call(token_params)
|
|
246
|
+
end
|
|
247
|
+
rescue StandardError => err
|
|
248
|
+
raise Ably::Exceptions::AuthenticationFailed.new("auth_callback failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: Ably::Exceptions::Codes::CONNECTION_NOT_ESTABLISHED_NO_TRANSPORT_HANDLE)
|
|
249
|
+
end
|
|
250
|
+
elsif auth_url = auth_options.delete(:auth_url)
|
|
251
|
+
begin
|
|
252
|
+
Timeout::timeout(client.auth_request_timeout) do
|
|
253
|
+
token_request_from_auth_url(auth_url, auth_options, token_params)
|
|
254
|
+
end
|
|
255
|
+
rescue StandardError => err
|
|
256
|
+
raise Ably::Exceptions::AuthenticationFailed.new("auth_url failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: Ably::Exceptions::Codes::CONNECTION_NOT_ESTABLISHED_NO_TRANSPORT_HANDLE)
|
|
257
|
+
end
|
|
258
|
+
else
|
|
259
|
+
create_token_request(token_params, auth_options)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
convert_to_token_details(token_request).tap do |token_details|
|
|
263
|
+
return token_details if token_details
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
send_token_request(token_request)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Creates and signs an {Ably::Models::TokenRequest} based on the specified (or if none specified, the client
|
|
270
|
+
# library stored) `token_params` and `auth_options`. Note this can only be used when the API key value is available
|
|
271
|
+
# locally. Otherwise, the {Ably::Models::TokenRequest} must be obtained from the key owner. Use this to generate
|
|
272
|
+
# an {Ably::Models::TokenRequest} in order to implement an Ably Token request callback for use by other clients.
|
|
273
|
+
# Both `token_params` and `auth_options` are optional. When omitted or null, the default token parameters
|
|
274
|
+
# and authentication options for the client library are used, as specified in the `client_options` when the client
|
|
275
|
+
# library was instantiated, or later updated with an explicit authorize request. Values passed in are used instead
|
|
276
|
+
# of, rather than being merged with, the default values. To understand why an {Ably::Models::TokenRequest} may be
|
|
277
|
+
# issued to clients in favor of a token, see Token Authentication explained.
|
|
278
|
+
#
|
|
279
|
+
# @spec RSA9
|
|
280
|
+
#
|
|
281
|
+
# @param [Hash] token_params the token params used in the token request
|
|
282
|
+
# @option token_params [String] :client_id A client ID to associate with this token. The generated token may be used to authenticate as this +client_id+
|
|
283
|
+
# @option token_params [Integer] :ttl validity time in seconds for the requested {Ably::Models::TokenDetails}. Limits may apply, see {https://www.ably.com/docs/general/authentication}
|
|
284
|
+
# @option token_params [Hash] :capability canonicalised representation of the resource paths and associated operations
|
|
285
|
+
# @option token_params [Time] :timestamp the time of the request
|
|
286
|
+
# @option token_params [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
|
287
|
+
#
|
|
288
|
+
# @param [Hash] auth_options the authentication options for the token request
|
|
289
|
+
# @option auth_options [String] :key API key comprising the key name and key secret in a single string
|
|
290
|
+
# @option auth_options [String] :client_id client ID identifying this connection to other clients (will use +client_id+ specified when library was instanced if provided)
|
|
291
|
+
# @option auth_options [Boolean] :query_time when true will query the {https://www.ably.com Ably} system for the current time instead of using the local time
|
|
292
|
+
# @option auth_options [Hash] :token_params convenience to pass in +token_params+ within the +auth_options+ argument, especially useful when setting default token_params in the client constructor
|
|
293
|
+
#
|
|
294
|
+
# @return [Models::TokenRequest]
|
|
295
|
+
#
|
|
296
|
+
# @example
|
|
297
|
+
# client.auth.create_token_request({ ttl: 3600 }, { id: 'asd.asd' })
|
|
298
|
+
# #<Ably::Models::TokenRequest:0x007fd5d919df78
|
|
299
|
+
# # @hash={
|
|
300
|
+
# # :id=>"asds.adsa",
|
|
301
|
+
# # :clientId=>nil,
|
|
302
|
+
# # :ttl=>3600000,
|
|
303
|
+
# # :timestamp=>1428973674000,
|
|
304
|
+
# # :capability=>"{\"*\":[\"*\"]}",
|
|
305
|
+
# # :nonce=>"95e543b88299f6bae83df9b12fbd1ecd",
|
|
306
|
+
# # :mac=>"881oZHeFo6oMim7....uE56a8gUxHw="
|
|
307
|
+
# # }
|
|
308
|
+
# #>>
|
|
309
|
+
def create_token_request(token_params = {}, auth_options = {})
|
|
310
|
+
ensure_valid_auth_attributes auth_options
|
|
311
|
+
|
|
312
|
+
auth_options = auth_options.dup
|
|
313
|
+
token_params = (auth_options[:token_params] || {}).merge(token_params)
|
|
314
|
+
|
|
315
|
+
split_api_key_into_key_and_secret! auth_options if auth_options[:key]
|
|
316
|
+
request_key_name = auth_options.delete(:key_name) || key_name
|
|
317
|
+
request_key_secret = auth_options.delete(:key_secret) || key_secret
|
|
318
|
+
|
|
319
|
+
raise Ably::Exceptions::TokenRequestFailed, 'Key Name and Key Secret are required to generate a new token request' unless request_key_name && request_key_secret
|
|
320
|
+
|
|
321
|
+
ensure_current_time_is_based_on_server_time if auth_options[:query_time]
|
|
322
|
+
timestamp = token_params.delete(:timestamp) || current_time
|
|
323
|
+
timestamp = Time.at(timestamp) if timestamp.kind_of?(Integer)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
token_request = {
|
|
328
|
+
keyName: request_key_name,
|
|
329
|
+
timestamp: (timestamp.to_f * 1000).round,
|
|
330
|
+
nonce: token_params[:nonce] || SecureRandom.hex.force_encoding('UTF-8')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
token_client_id = token_params[:client_id] || auth_options[:client_id] || client_id
|
|
334
|
+
token_request[:clientId] = token_client_id if token_client_id
|
|
335
|
+
|
|
336
|
+
if token_params[:ttl]
|
|
337
|
+
token_ttl = [
|
|
338
|
+
token_params[:ttl],
|
|
339
|
+
Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER + TOKEN_DEFAULTS.fetch(:renew_token_buffer) # never issue a token that will be immediately considered expired due to the buffer
|
|
340
|
+
].max
|
|
341
|
+
token_request[:ttl] = (token_ttl * 1000).to_i
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
token_request[:capability] = token_params[:capability] if token_params[:capability]
|
|
345
|
+
if token_request[:capability].is_a?(Hash)
|
|
346
|
+
lexicographic_ordered_capabilities = Hash[
|
|
347
|
+
token_request[:capability].sort_by { |key, value| key }.map do |key, value|
|
|
348
|
+
[key, value.sort]
|
|
349
|
+
end
|
|
350
|
+
]
|
|
351
|
+
token_request[:capability] = JSON.dump(lexicographic_ordered_capabilities)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
token_request[:mac] = sign_params(token_request, request_key_secret)
|
|
355
|
+
|
|
356
|
+
# Undocumented feature to request a persisted token
|
|
357
|
+
token_request[:persisted] = token_params[:persisted] if token_params[:persisted]
|
|
358
|
+
|
|
359
|
+
Models::TokenRequest.new(token_request)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def key
|
|
363
|
+
"#{key_name}:#{key_secret}" if api_key_present?
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def key_name
|
|
367
|
+
@key_name
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def key_secret
|
|
371
|
+
@key_secret
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# True when Basic Auth is being used to authenticate with Ably
|
|
375
|
+
def using_basic_auth?
|
|
376
|
+
!using_token_auth?
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# True when Token Auth is being used to authenticate with Ably
|
|
380
|
+
def using_token_auth?
|
|
381
|
+
return options[:use_token_auth] if options.has_key?(:use_token_auth)
|
|
382
|
+
!!(token_option || current_token_details || token_creatable_externally?)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def client_id
|
|
386
|
+
@client_id || options[:client_id]
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# When a client has authenticated with Ably and the client is either anonymous (cannot assume a +client_id+)
|
|
390
|
+
# or has an assigned +client_id+ (implicit in all operations), then this client has a validated +client_id+, even
|
|
391
|
+
# if that client_id is +nil+ (anonymous)
|
|
392
|
+
#
|
|
393
|
+
# Once validated by Ably, the client library will enforce the use of the +client_id+ identity provided by Ably, rejecting
|
|
394
|
+
# messages with an invalid +client_id+ immediately
|
|
395
|
+
#
|
|
396
|
+
# @return [Boolean]
|
|
397
|
+
def client_id_validated?
|
|
398
|
+
!!@client_id_validated
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Auth header string used in HTTP requests to Ably
|
|
402
|
+
# Will reauthorize implicitly if required and capable
|
|
403
|
+
#
|
|
404
|
+
# @return [String] HTTP authentication value used in HTTP_AUTHORIZATION header
|
|
405
|
+
def auth_header
|
|
406
|
+
if using_token_auth?
|
|
407
|
+
token_auth_header
|
|
408
|
+
else
|
|
409
|
+
basic_auth_header
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Extra headers that may be used during authentication
|
|
414
|
+
#
|
|
415
|
+
# @return [Hash] headers
|
|
416
|
+
def extra_auth_headers
|
|
417
|
+
if client_id_for_request
|
|
418
|
+
{ 'X-Ably-ClientId' => Base64.urlsafe_encode64(client_id_for_request) }
|
|
419
|
+
else
|
|
420
|
+
{}
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# ClientId that needs to be included with every rest/realtime request
|
|
425
|
+
# spec - RSA7e
|
|
426
|
+
# @return string
|
|
427
|
+
def client_id_for_request
|
|
428
|
+
options[:client_id]
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Auth params used in URI endpoint for Realtime connections
|
|
432
|
+
# Will reauthorize implicitly if required and capable
|
|
433
|
+
#
|
|
434
|
+
# @return [Hash] Auth params for a new Realtime connection
|
|
435
|
+
def auth_params
|
|
436
|
+
if using_token_auth?
|
|
437
|
+
token_auth_params
|
|
438
|
+
else
|
|
439
|
+
basic_auth_params
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# True if prerequisites for creating a new token request are present
|
|
444
|
+
#
|
|
445
|
+
# One of the following criterion must be met:
|
|
446
|
+
# * Valid API key and token option not provided as token options cannot be determined
|
|
447
|
+
# * Authentication callback for new token requests
|
|
448
|
+
# * Authentication URL for new token requests
|
|
449
|
+
#
|
|
450
|
+
# @return [Boolean]
|
|
451
|
+
def token_renewable?
|
|
452
|
+
token_creatable_externally? || (api_key_present? && !token_option)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Returns false when attempting to send an API Key over a non-secure connection
|
|
456
|
+
# Token auth must be used for non-secure connections
|
|
457
|
+
#
|
|
458
|
+
# @return [Boolean]
|
|
459
|
+
def authentication_security_requirements_met?
|
|
460
|
+
client.use_tls? || using_token_auth?
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# True if token provided client_id is compatible with the client's configured +client_id+, when applicable
|
|
464
|
+
#
|
|
465
|
+
# @return [Boolean]
|
|
466
|
+
# @api private
|
|
467
|
+
def token_client_id_allowed?(token_client_id)
|
|
468
|
+
return true if client_id.nil? # no explicit client_id specified for this client
|
|
469
|
+
return true if client_id == '*' || token_client_id == '*' # wildcard supported always
|
|
470
|
+
token_client_id == client_id
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# True if assumed_client_id is compatible with the client's configured or Ably assigned +client_id+
|
|
474
|
+
#
|
|
475
|
+
# @return [Boolean]
|
|
476
|
+
# @api private
|
|
477
|
+
def can_assume_client_id?(assumed_client_id)
|
|
478
|
+
if client_id_validated?
|
|
479
|
+
client_id == '*' || (client_id == assumed_client_id)
|
|
480
|
+
elsif !options[:client_id] || options[:client_id] == '*'
|
|
481
|
+
true # client ID is unknown
|
|
482
|
+
else
|
|
483
|
+
options[:client_id] == assumed_client_id
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Configures the client ID for this client
|
|
488
|
+
# Typically this occurs following an Auth or receiving a {Ably::Models::ProtocolMessage} with a +client_id+ in the {Ably::Models::ConnectionDetails}
|
|
489
|
+
#
|
|
490
|
+
# @api private
|
|
491
|
+
def configure_client_id(new_client_id)
|
|
492
|
+
if has_client_id?
|
|
493
|
+
# If new client ID from Ably is a wildcard, but preconfigured clientId is set, then keep the existing clientId
|
|
494
|
+
if new_client_id == "*"
|
|
495
|
+
@client_id_validated = true
|
|
496
|
+
return
|
|
497
|
+
end
|
|
498
|
+
# If client_id is defined and not a wildcard, prevent it changing, this is not supported
|
|
499
|
+
if new_client_id != client_id
|
|
500
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Client ID is immutable once configured for a client. Client ID cannot be changed to '#{new_client_id}'")
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
@client_id_validated = true
|
|
504
|
+
@client_id = new_client_id
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# True when a client_id other than a wildcard is configured for Auth
|
|
508
|
+
#
|
|
509
|
+
# @api private
|
|
510
|
+
def has_client_id?
|
|
511
|
+
client_id && (client_id != '*')
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
private
|
|
515
|
+
def client
|
|
516
|
+
@client
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def token_option
|
|
520
|
+
@token_option
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def authorize_when_necessary
|
|
524
|
+
if current_token_details && !current_token_details.expired?(from: current_time)
|
|
525
|
+
return current_token_details
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
authorize
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Returns the current device clock time unless the
|
|
532
|
+
# the server time has previously been requested with query_time: true
|
|
533
|
+
# and the @server_time_offset is configured
|
|
534
|
+
def current_time
|
|
535
|
+
if @server_time_offset
|
|
536
|
+
Time.now + @server_time_offset
|
|
537
|
+
else
|
|
538
|
+
Time.now
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Get the difference in time between the server
|
|
543
|
+
# and the local clock and store this for future time requests
|
|
544
|
+
def ensure_current_time_is_based_on_server_time
|
|
545
|
+
server_time = client.time
|
|
546
|
+
@server_time_offset = server_time.to_f - Time.now.to_f
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def ensure_valid_auth_attributes(attributes)
|
|
550
|
+
(attributes.keys.map(&:to_s) - AUTH_OPTIONS_KEYS).tap do |unsupported_keys|
|
|
551
|
+
raise ArgumentError, "The key(s) #{unsupported_keys.map { |k| ":#{k}" }.join(', ')} are not valid AuthOptions" unless unsupported_keys.empty?
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
if attributes[:timestamp]
|
|
555
|
+
unless attributes[:timestamp].kind_of?(Time) || attributes[:timestamp].kind_of?(Numeric)
|
|
556
|
+
raise ArgumentError, ':timestamp must be a Time or positive Integer value of seconds since epoch'
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
if attributes[:ttl]
|
|
561
|
+
unless attributes[:ttl].kind_of?(Numeric) && attributes[:ttl].to_f > 0
|
|
562
|
+
raise ArgumentError, ':ttl must be a positive Numeric value representing time to live in seconds'
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
if attributes[:auth_headers]
|
|
567
|
+
unless attributes[:auth_headers].kind_of?(Hash)
|
|
568
|
+
raise ArgumentError, ':auth_headers must be a valid Hash'
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
if attributes[:auth_params]
|
|
573
|
+
unless attributes[:auth_params].kind_of?(Hash)
|
|
574
|
+
raise ArgumentError, ':auth_params must be a valid Hash'
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
if attributes[:auth_method]
|
|
579
|
+
unless %(get post).include?(attributes[:auth_method].to_s)
|
|
580
|
+
raise ArgumentError, ':auth_method must be either :get or :post'
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
if attributes[:auth_callback]
|
|
585
|
+
unless attributes[:auth_callback].respond_to?(:call)
|
|
586
|
+
raise ArgumentError, ':auth_callback must be a Proc'
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def ensure_api_key_sent_over_secure_connection
|
|
592
|
+
raise Ably::Exceptions::InsecureRequest, 'Cannot use Basic Auth over non-TLS connections' unless authentication_security_requirements_met?
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Basic Auth HTTP Authorization header value
|
|
596
|
+
def basic_auth_header
|
|
597
|
+
ensure_api_key_sent_over_secure_connection
|
|
598
|
+
"Basic #{encode64("#{key}")}"
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def split_api_key_into_key_and_secret!(options)
|
|
602
|
+
api_key_parts = options[:key].to_s.match(/(?<name>[\w-]+\.[\w-]+):(?<secret>[\w-]+)/)
|
|
603
|
+
raise ArgumentError, 'key is invalid' unless api_key_parts
|
|
604
|
+
|
|
605
|
+
options[:key_name] = api_key_parts[:name].encode(Encoding::UTF_8)
|
|
606
|
+
options[:key_secret] = api_key_parts[:secret].encode(Encoding::UTF_8)
|
|
607
|
+
|
|
608
|
+
options.delete :key
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def store_and_delete_basic_auth_key_from_options!(options)
|
|
612
|
+
@key_name = options.delete(:key_name)
|
|
613
|
+
@key_secret = options.delete(:key_secret)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# Returns the current token if it exists or authorizes and retrieves a token
|
|
617
|
+
def token_auth_string
|
|
618
|
+
if !current_token_details && token_option
|
|
619
|
+
logger.debug { "Auth: Token auth string missing, authorizing implicitly now" }
|
|
620
|
+
# A TokenRequest was configured in the ClientOptions +:token field+ and no current token exists
|
|
621
|
+
# Note: If a Token or TokenDetails is provided in the initializer, the token is stored in +current_token_details+
|
|
622
|
+
authorize_with_token send_token_request(token_option)
|
|
623
|
+
current_token_details.token
|
|
624
|
+
else
|
|
625
|
+
# Authorize will use the current token if one exists and is not expired, otherwise a new token will be issued
|
|
626
|
+
authorize_when_necessary.token
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def configure_current_token_details(token_details)
|
|
631
|
+
@current_token_details = token_details
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# Token Auth HTTP Authorization header value
|
|
635
|
+
def token_auth_header
|
|
636
|
+
"Bearer #{encode64(token_auth_string)}"
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Basic Auth params to authenticate the Realtime connection
|
|
640
|
+
def basic_auth_params
|
|
641
|
+
ensure_api_key_sent_over_secure_connection
|
|
642
|
+
{
|
|
643
|
+
key: key
|
|
644
|
+
}
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Token Auth params to authenticate the Realtime connection
|
|
648
|
+
def token_auth_params
|
|
649
|
+
{
|
|
650
|
+
access_token: token_auth_string
|
|
651
|
+
}
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# Sign the request params using the secret
|
|
655
|
+
#
|
|
656
|
+
# @return [Hash]
|
|
657
|
+
def sign_params(params, secret)
|
|
658
|
+
text = params.values_at(
|
|
659
|
+
:keyName,
|
|
660
|
+
:ttl,
|
|
661
|
+
:capability,
|
|
662
|
+
:clientId,
|
|
663
|
+
:timestamp,
|
|
664
|
+
:nonce
|
|
665
|
+
).map do |val|
|
|
666
|
+
"#{val}\n"
|
|
667
|
+
end.join('')
|
|
668
|
+
|
|
669
|
+
encode64(
|
|
670
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
|
|
671
|
+
)
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Retrieve a token request from a specified URL, expects a JSON or text response
|
|
675
|
+
#
|
|
676
|
+
# @return [Hash]
|
|
677
|
+
def token_request_from_auth_url(auth_url, auth_options, token_params)
|
|
678
|
+
uri = URI.parse(auth_url)
|
|
679
|
+
connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
|
|
680
|
+
method = auth_options[:auth_method] || options[:auth_method] || :get
|
|
681
|
+
params = (auth_options[:auth_params] || options[:auth_params] || {}).merge(token_params)
|
|
682
|
+
|
|
683
|
+
response = connection.public_send(method) do |request|
|
|
684
|
+
request.url uri.path
|
|
685
|
+
request.headers = auth_options[:auth_headers] || {}
|
|
686
|
+
if method.to_s.downcase == 'post'
|
|
687
|
+
request.body = params
|
|
688
|
+
else
|
|
689
|
+
request.params = (Addressable::URI.parse(uri.to_s).query_values || {}).merge(params)
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
if !response.body.kind_of?(Hash) && !response.headers['Content-Type'].to_s.match(%r{text/plain|application/jwt}i)
|
|
694
|
+
raise Ably::Exceptions::InvalidResponseBody,
|
|
695
|
+
"Content Type #{response.headers['Content-Type']} is not supported by this client library"
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
response.body
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Use the provided token to authenticate immediately and store the token details in +current_token_details+
|
|
702
|
+
def authorize_with_token(new_token_details)
|
|
703
|
+
if new_token_details && !new_token_details.from_token_string?
|
|
704
|
+
if !token_client_id_allowed?(new_token_details.client_id)
|
|
705
|
+
raise Ably::Exceptions::IncompatibleClientId.new("Client ID '#{new_token_details.client_id}' in the token is incompatible with the current client ID '#{client_id}'")
|
|
706
|
+
end
|
|
707
|
+
configure_client_id new_token_details.client_id
|
|
708
|
+
end
|
|
709
|
+
configure_current_token_details new_token_details
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# Returns a TokenDetails object if the provided token_details_obj argument is a TokenDetails object, Token String
|
|
713
|
+
# or TokenDetails JSON object.
|
|
714
|
+
# If the token_details_obj is not a Token or TokenDetails +nil+ is returned
|
|
715
|
+
def convert_to_token_details(token_details_obj)
|
|
716
|
+
case token_details_obj
|
|
717
|
+
when Ably::Models::TokenDetails
|
|
718
|
+
return token_details_obj
|
|
719
|
+
when Hash
|
|
720
|
+
return Ably::Models::TokenDetails.new(token_details_obj) if IdiomaticRubyWrapper(token_details_obj).has_key?(:issued)
|
|
721
|
+
when String
|
|
722
|
+
return Ably::Models::TokenDetails.new(token: token_details_obj)
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# @return [Ably::Models::TokenDetails]
|
|
727
|
+
def send_token_request(token_request)
|
|
728
|
+
token_request = Ably::Models::TokenRequest(token_request)
|
|
729
|
+
|
|
730
|
+
response = client.post("/keys/#{token_request.key_name}/requestToken",
|
|
731
|
+
token_request.attributes, send_auth_header: false,
|
|
732
|
+
disable_automatic_reauthorize: true)
|
|
733
|
+
|
|
734
|
+
Ably::Models::TokenDetails.new(response.body)
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
# Return a Hash of connection options to initiate the Faraday::Connection with
|
|
738
|
+
#
|
|
739
|
+
# @return [Hash]
|
|
740
|
+
def connection_options
|
|
741
|
+
@connection_options ||= {
|
|
742
|
+
builder: middleware,
|
|
743
|
+
headers: {
|
|
744
|
+
accept: client.mime_type,
|
|
745
|
+
user_agent: user_agent
|
|
746
|
+
},
|
|
747
|
+
request: {
|
|
748
|
+
open_timeout: 5,
|
|
749
|
+
timeout: 10
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
# Return a Faraday middleware stack to initiate the Faraday::Connection with
|
|
755
|
+
#
|
|
756
|
+
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
|
757
|
+
def middleware
|
|
758
|
+
@middleware ||= Faraday::RackBuilder.new do |builder|
|
|
759
|
+
setup_outgoing_middleware builder
|
|
760
|
+
|
|
761
|
+
# Raise exceptions if response code is invalid
|
|
762
|
+
builder.use Ably::Rest::Middleware::ExternalExceptions
|
|
763
|
+
|
|
764
|
+
setup_incoming_middleware builder, logger
|
|
765
|
+
|
|
766
|
+
# Set Faraday's HTTP adapter
|
|
767
|
+
builder.adapter Faraday.default_adapter
|
|
768
|
+
end
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
def auth_callback_present?
|
|
772
|
+
!!options[:auth_callback]
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def token_url_present?
|
|
776
|
+
!!options[:auth_url]
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def token_creatable_externally?
|
|
780
|
+
auth_callback_present? || token_url_present?
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
def api_key_present?
|
|
784
|
+
key_name && key_secret
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def logger
|
|
788
|
+
client.logger
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
end
|