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.
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