ably-rest 0.7.1 → 0.7.3
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 +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'ably/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'ably'
|
|
8
|
+
spec.version = Ably::VERSION
|
|
9
|
+
spec.authors = ['Lewis Marshall', "Matthew O'Riordan"]
|
|
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}
|
|
13
|
+
spec.homepage = 'http://github.com/ably/ably-ruby'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
spec.add_runtime_dependency 'eventmachine', '~> 1.0'
|
|
22
|
+
spec.add_runtime_dependency 'em-http-request', '~> 1.1'
|
|
23
|
+
spec.add_runtime_dependency 'statesman', '~> 1.0.0'
|
|
24
|
+
spec.add_runtime_dependency 'faraday', '~> 0.9'
|
|
25
|
+
spec.add_runtime_dependency 'json'
|
|
26
|
+
spec.add_runtime_dependency 'websocket-driver', '~> 0.3'
|
|
27
|
+
spec.add_runtime_dependency 'msgpack-ably', '~> 0.5.10'
|
|
28
|
+
|
|
29
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
|
30
|
+
spec.add_development_dependency 'rake'
|
|
31
|
+
spec.add_development_dependency 'redcarpet'
|
|
32
|
+
spec.add_development_dependency 'rspec', '~> 3.1.0' # version lock, see config.around(:example, :event_machine) in event_machine_helper.rb
|
|
33
|
+
spec.add_development_dependency 'rspec-retry'
|
|
34
|
+
spec.add_development_dependency 'yard'
|
|
35
|
+
spec.add_development_dependency 'webmock'
|
|
36
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
%w(modules util).each do |namespace|
|
|
2
|
+
Dir.glob(File.expand_path("ably/#{namespace}/*.rb", File.dirname(__FILE__))).each do |file|
|
|
3
|
+
require file
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'ably/auth'
|
|
8
|
+
require 'ably/exceptions'
|
|
9
|
+
require 'ably/logger'
|
|
10
|
+
require 'ably/realtime'
|
|
11
|
+
require 'ably/rest'
|
|
12
|
+
require 'ably/version'
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'faraday'
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
require 'ably/rest/middleware/external_exceptions'
|
|
6
|
+
|
|
7
|
+
module Ably
|
|
8
|
+
# Auth is responsible for authentication with {https://ably.io Ably} using basic or token authentication
|
|
9
|
+
#
|
|
10
|
+
# Find out more about Ably authentication at: http://docs.ably.io/other/authentication/
|
|
11
|
+
#
|
|
12
|
+
# @!attribute [r] client_id
|
|
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
|
|
22
|
+
# @!attribute [r] key_secret
|
|
23
|
+
# @return [String] Key secret (private secure part of the API key), if present
|
|
24
|
+
# @!attribute [r] options
|
|
25
|
+
# @return [Hash] {Ably::Auth} options configured for this client
|
|
26
|
+
|
|
27
|
+
class Auth
|
|
28
|
+
include Ably::Modules::Conversions
|
|
29
|
+
include Ably::Modules::HttpHelpers
|
|
30
|
+
|
|
31
|
+
attr_reader :options, :current_token
|
|
32
|
+
alias_method :auth_options, :options
|
|
33
|
+
|
|
34
|
+
# Creates an Auth object
|
|
35
|
+
#
|
|
36
|
+
# @param [Ably::Rest::Client] client {Ably::Rest::Client} this Auth object uses
|
|
37
|
+
# @param options (see Ably::Rest::Client#initialize)
|
|
38
|
+
# @option (see Ably::Rest::Client#initialize)
|
|
39
|
+
# @yield (see Ably::Rest::Client#initialize)
|
|
40
|
+
#
|
|
41
|
+
def initialize(client, options, &token_request_block)
|
|
42
|
+
auth_options = options.dup
|
|
43
|
+
|
|
44
|
+
@client = client
|
|
45
|
+
@options = auth_options
|
|
46
|
+
@default_token_block = token_request_block if block_given?
|
|
47
|
+
|
|
48
|
+
unless auth_options.kind_of?(Hash)
|
|
49
|
+
raise ArgumentError, 'Expected auth_options to be a Hash'
|
|
50
|
+
end
|
|
51
|
+
|
|
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'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if auth_options[:api_key]
|
|
57
|
+
api_key_parts = auth_options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
|
|
58
|
+
raise ArgumentError, 'api_key is invalid' unless api_key_parts
|
|
59
|
+
auth_options[:key_id] = api_key_parts[:id].encode(Encoding::UTF_8)
|
|
60
|
+
auth_options[:key_secret] = api_key_parts[:secret].encode(Encoding::UTF_8)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if using_basic_auth? && !api_key_present?
|
|
64
|
+
raise ArgumentError, 'api_key is missing. Either an API key, token, or token auth method must be provided'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if has_client_id?
|
|
68
|
+
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
|
+
ensure_utf_8 :client_id, client_id
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@options.freeze
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Ensures valid auth credentials are present for the library instance. This may rely on an already-known and valid token, and will obtain a new token if necessary.
|
|
76
|
+
#
|
|
77
|
+
# In the event that a new token request is made, the specified options are used.
|
|
78
|
+
#
|
|
79
|
+
# @param [Hash] options the options for the token request
|
|
80
|
+
# @option options (see #request_token)
|
|
81
|
+
# @option options [Boolean] :force obtains a new token even if the current token is valid
|
|
82
|
+
#
|
|
83
|
+
# @yield (see #request_token)
|
|
84
|
+
# @yieldparam [Hash] options options passed to {#authorise} will be in turn sent to the block in this argument
|
|
85
|
+
# @yieldreturn (see #request_token)
|
|
86
|
+
#
|
|
87
|
+
# @return (see #request_token)
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# # will issue a simple token request using basic auth
|
|
91
|
+
# client = Ably::Rest::Client.new(api_key: 'key.id:secret')
|
|
92
|
+
# token = client.auth.authorise
|
|
93
|
+
#
|
|
94
|
+
# # will use token request from block to authorise if not already authorised
|
|
95
|
+
# token = client.auth.authorise do |options|
|
|
96
|
+
# # create token_request object
|
|
97
|
+
# token_request
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
def authorise(options = {}, &token_request_block)
|
|
101
|
+
if !options[:force] && current_token
|
|
102
|
+
return current_token unless current_token.expired?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
@options = @options.merge(options)
|
|
106
|
+
@default_token_block = token_request_block if block_given?
|
|
107
|
+
|
|
108
|
+
@current_token = request_token(options, &token_request_block)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Request a {Ably::Models::Token} which can be used to make authenticated token based requests
|
|
112
|
+
#
|
|
113
|
+
# @param [Hash] options the options for the token request
|
|
114
|
+
# @option options [String] :key_id key ID for the designated application (defaults to client key_id)
|
|
115
|
+
# @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
|
|
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 [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
|
122
|
+
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
|
123
|
+
# @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
|
|
124
|
+
# @option options [Time] :timestamp the time of the of the request
|
|
125
|
+
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
|
126
|
+
#
|
|
127
|
+
# @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
|
|
128
|
+
# @yieldparam [Hash] options options passed to {#request_token} will be in turn sent to the block in this argument
|
|
129
|
+
# @yieldreturn [Hash] expects a valid token request object, see {#create_token_request}
|
|
130
|
+
#
|
|
131
|
+
# @return [Ably::Models::Token]
|
|
132
|
+
#
|
|
133
|
+
# @example
|
|
134
|
+
# # simple token request using basic auth
|
|
135
|
+
# client = Ably::Rest::Client.new(api_key: 'key.id:secret')
|
|
136
|
+
# token = client.auth.request_token
|
|
137
|
+
#
|
|
138
|
+
# # token request using auth block
|
|
139
|
+
# token = client.auth.request_token do |options|
|
|
140
|
+
# # create token_request object
|
|
141
|
+
# token_request
|
|
142
|
+
# end
|
|
143
|
+
#
|
|
144
|
+
def request_token(options = {})
|
|
145
|
+
token_options = self.auth_options.merge(options)
|
|
146
|
+
|
|
147
|
+
auth_url = token_options.delete(:auth_url)
|
|
148
|
+
token_request = if block_given?
|
|
149
|
+
yield token_options
|
|
150
|
+
elsif default_token_block
|
|
151
|
+
default_token_block.call(token_options)
|
|
152
|
+
elsif auth_url
|
|
153
|
+
token_request_from_auth_url(auth_url, token_options)
|
|
154
|
+
else
|
|
155
|
+
create_token_request(token_options)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
token_request = IdiomaticRubyWrapper(token_request)
|
|
159
|
+
|
|
160
|
+
response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request.hash, send_auth_header: false, disable_automatic_reauthorise: true)
|
|
161
|
+
body = IdiomaticRubyWrapper(response.body)
|
|
162
|
+
|
|
163
|
+
Ably::Models::Token.new(body.fetch(:access_token))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Creates and signs a token request that can then subsequently be used by any client to request a token
|
|
167
|
+
#
|
|
168
|
+
# @param [Hash] options the options for the token request
|
|
169
|
+
# @option options [String] :key_id key ID for the designated application
|
|
170
|
+
# @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
|
|
171
|
+
# @option options [String] :client_id client ID identifying this connection to other clients
|
|
172
|
+
# @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
|
173
|
+
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
|
174
|
+
# @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
|
|
175
|
+
# @option options [Time] :timestamp the time of the of the request
|
|
176
|
+
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
|
177
|
+
# @return [Hash]
|
|
178
|
+
#
|
|
179
|
+
# @example
|
|
180
|
+
# client.auth.create_request_token(id: 'asd.asd', ttl: 3600)
|
|
181
|
+
# # => {
|
|
182
|
+
# # :id=>"asds.adsa",
|
|
183
|
+
# # :client_id=>nil,
|
|
184
|
+
# # :ttl=>3600,
|
|
185
|
+
# # :timestamp=>1410718527,
|
|
186
|
+
# # :capability=>"{\"*\":[\"*\"]}",
|
|
187
|
+
# # :nonce=>"95e543b88299f6bae83df9b12fbd1ecd",
|
|
188
|
+
# # :mac=>"881oZHeFo6oMim7N64y2vFHtSlpQ2gn/uE56a8gUxHw="
|
|
189
|
+
# # }
|
|
190
|
+
def create_token_request(options = {})
|
|
191
|
+
token_attributes = %w(id client_id ttl timestamp capability nonce)
|
|
192
|
+
|
|
193
|
+
token_options = options.clone
|
|
194
|
+
request_key_id = token_options.delete(:key_id) || key_id
|
|
195
|
+
request_key_secret = token_options.delete(:key_secret) || key_secret
|
|
196
|
+
|
|
197
|
+
raise Ably::Exceptions::TokenRequestError, 'Key ID and Key Secret are required to generate a new token request' unless request_key_id && request_key_secret
|
|
198
|
+
|
|
199
|
+
timestamp = if token_options[:query_time]
|
|
200
|
+
client.time
|
|
201
|
+
else
|
|
202
|
+
token_options.delete(:timestamp) || Time.now
|
|
203
|
+
end.to_i
|
|
204
|
+
|
|
205
|
+
token_request = {
|
|
206
|
+
id: request_key_id,
|
|
207
|
+
clientId: client_id,
|
|
208
|
+
ttl: Ably::Models::Token::DEFAULTS[:ttl],
|
|
209
|
+
timestamp: timestamp,
|
|
210
|
+
capability: Ably::Models::Token::DEFAULTS[:capability],
|
|
211
|
+
nonce: SecureRandom.hex
|
|
212
|
+
}.merge(token_options.select { |key, val| token_attributes.include?(key.to_s) })
|
|
213
|
+
|
|
214
|
+
if token_request[:capability].is_a?(Hash)
|
|
215
|
+
token_request[:capability] = token_request[:capability].to_json
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
ensure_utf_8 :nonce, token_request[:nonce], allow_nil: true
|
|
219
|
+
|
|
220
|
+
token_request[:mac] = sign_params(token_request, request_key_secret)
|
|
221
|
+
|
|
222
|
+
convert_to_mixed_case_hash(token_request)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def api_key
|
|
226
|
+
"#{key_id}:#{key_secret}" if api_key_present?
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def key_id
|
|
230
|
+
options[:key_id]
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def key_secret
|
|
234
|
+
options[:key_secret]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# True when Basic Auth is being used to authenticate with Ably
|
|
238
|
+
def using_basic_auth?
|
|
239
|
+
!using_token_auth?
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# True when Token Auth is being used to authenticate with Ably
|
|
243
|
+
def using_token_auth?
|
|
244
|
+
return options[:use_token_auth] if options.has_key?(:use_token_auth)
|
|
245
|
+
token_id || current_token || has_client_id? || token_creatable_externally?
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def client_id
|
|
249
|
+
options[:client_id]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def token_id
|
|
253
|
+
options[:token_id]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Auth header string used in HTTP requests to Ably
|
|
257
|
+
#
|
|
258
|
+
# @return [String] HTTP authentication value used in HTTP_AUTHORIZATION header
|
|
259
|
+
def auth_header
|
|
260
|
+
if using_token_auth?
|
|
261
|
+
token_auth_header
|
|
262
|
+
else
|
|
263
|
+
basic_auth_header
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Auth params used in URI endpoint for Realtime connections
|
|
268
|
+
#
|
|
269
|
+
# @return [Hash] Auth params for a new Realtime connection
|
|
270
|
+
def auth_params
|
|
271
|
+
if using_token_auth?
|
|
272
|
+
token_auth_params
|
|
273
|
+
else
|
|
274
|
+
basic_auth_params
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# True if prerequisites for creating a new token request are present
|
|
279
|
+
#
|
|
280
|
+
# One of the following criterion must be met:
|
|
281
|
+
# * Valid key id and secret and token_id option not provided as token options cannot be determined
|
|
282
|
+
# * Authentication callback for new token requests
|
|
283
|
+
# * Authentication URL for new token requests
|
|
284
|
+
#
|
|
285
|
+
# @return [Boolean]
|
|
286
|
+
def token_renewable?
|
|
287
|
+
token_creatable_externally? || (api_key_present? && !token_id)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Returns false when attempting to send an API Key over a non-secure connection
|
|
291
|
+
# Token auth must be used for non-secure connections
|
|
292
|
+
#
|
|
293
|
+
# @return [Boolean]
|
|
294
|
+
def authentication_security_requirements_met?
|
|
295
|
+
client.use_tls? || using_token_auth?
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
private
|
|
299
|
+
attr_reader :default_token_block
|
|
300
|
+
|
|
301
|
+
def ensure_api_key_sent_over_secure_connection
|
|
302
|
+
raise Ably::Exceptions::InsecureRequestError, 'Cannot use Basic Auth over non-TLS connections' unless authentication_security_requirements_met?
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Basic Auth HTTP Authorization header value
|
|
306
|
+
def basic_auth_header
|
|
307
|
+
ensure_api_key_sent_over_secure_connection
|
|
308
|
+
"Basic #{encode64("#{api_key}")}"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def token_auth_id
|
|
312
|
+
if token_id
|
|
313
|
+
token_id
|
|
314
|
+
else
|
|
315
|
+
authorise.id
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Token Auth HTTP Authorization header value
|
|
320
|
+
def token_auth_header
|
|
321
|
+
"Bearer #{encode64(token_auth_id)}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Basic Auth params to authenticate the Realtime connection
|
|
325
|
+
def basic_auth_params
|
|
326
|
+
ensure_api_key_sent_over_secure_connection
|
|
327
|
+
# TODO: Change to key_secret when API is updated
|
|
328
|
+
{
|
|
329
|
+
key_id: key_id,
|
|
330
|
+
key_value: key_secret
|
|
331
|
+
}
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Token Auth params to authenticate the Realtime connection
|
|
335
|
+
def token_auth_params
|
|
336
|
+
{
|
|
337
|
+
access_token: token_auth_id
|
|
338
|
+
}
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Sign the request params using the secret
|
|
342
|
+
#
|
|
343
|
+
# @return [Hash]
|
|
344
|
+
def sign_params(params, secret)
|
|
345
|
+
text = params.values_at(
|
|
346
|
+
:id,
|
|
347
|
+
:ttl,
|
|
348
|
+
:capability,
|
|
349
|
+
:client_id,
|
|
350
|
+
:timestamp,
|
|
351
|
+
:nonce
|
|
352
|
+
).map { |t| "#{t}\n" }.join("")
|
|
353
|
+
|
|
354
|
+
encode64(
|
|
355
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
|
|
356
|
+
)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Retrieve a token request from a specified URL, expects a JSON response
|
|
360
|
+
#
|
|
361
|
+
# @return [Hash]
|
|
362
|
+
def token_request_from_auth_url(auth_url, options = {})
|
|
363
|
+
uri = URI.parse(auth_url)
|
|
364
|
+
connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
|
|
365
|
+
method = options[:auth_method] || :get
|
|
366
|
+
|
|
367
|
+
response = connection.send(method) do |request|
|
|
368
|
+
request.url uri.path
|
|
369
|
+
request.params = options[:auth_params] || {}
|
|
370
|
+
request.headers = options[:auth_headers] || {}
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
unless response.body.kind_of?(Hash)
|
|
374
|
+
raise Ably::Exceptions::InvalidResponseBody,
|
|
375
|
+
"Content Type #{response.headers['Content-Type']} is not supported by this client library"
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
response.body
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Return a Hash of connection options to initiate the Faraday::Connection with
|
|
382
|
+
#
|
|
383
|
+
# @return [Hash]
|
|
384
|
+
def connection_options
|
|
385
|
+
@connection_options ||= {
|
|
386
|
+
builder: middleware,
|
|
387
|
+
headers: {
|
|
388
|
+
accept: client.mime_type,
|
|
389
|
+
user_agent: user_agent
|
|
390
|
+
},
|
|
391
|
+
request: {
|
|
392
|
+
open_timeout: 5,
|
|
393
|
+
timeout: 10
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Return a Faraday middleware stack to initiate the Faraday::Connection with
|
|
399
|
+
#
|
|
400
|
+
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
|
401
|
+
def middleware
|
|
402
|
+
@middleware ||= Faraday::RackBuilder.new do |builder|
|
|
403
|
+
setup_outgoing_middleware builder
|
|
404
|
+
|
|
405
|
+
# Raise exceptions if response code is invalid
|
|
406
|
+
builder.use Ably::Rest::Middleware::ExternalExceptions
|
|
407
|
+
|
|
408
|
+
setup_incoming_middleware builder, client.logger
|
|
409
|
+
|
|
410
|
+
# Set Faraday's HTTP adapter
|
|
411
|
+
builder.adapter Faraday.default_adapter
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def token_callback_present?
|
|
416
|
+
!!default_token_block
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def token_url_present?
|
|
420
|
+
!!options[:auth_url]
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def token_creatable_externally?
|
|
424
|
+
token_callback_present? || token_url_present?
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def has_client_id?
|
|
428
|
+
!!client_id
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def api_key_present?
|
|
432
|
+
key_id && key_secret
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
private
|
|
436
|
+
attr_reader :client
|
|
437
|
+
end
|
|
438
|
+
end
|