ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. 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