ably 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +4 -0
- data/ably.gemspec +32 -0
- data/lib/ably.rb +11 -0
- data/lib/ably/auth.rb +381 -0
- data/lib/ably/exceptions.rb +16 -0
- data/lib/ably/realtime.rb +38 -0
- data/lib/ably/realtime/callbacks.rb +15 -0
- data/lib/ably/realtime/channel.rb +51 -0
- data/lib/ably/realtime/client.rb +82 -0
- data/lib/ably/realtime/connection.rb +61 -0
- data/lib/ably/rest.rb +15 -0
- data/lib/ably/rest/channel.rb +58 -0
- data/lib/ably/rest/client.rb +194 -0
- data/lib/ably/rest/middleware/exceptions.rb +42 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +26 -0
- data/lib/ably/rest/middleware/parse_json.rb +15 -0
- data/lib/ably/rest/paged_resource.rb +107 -0
- data/lib/ably/rest/presence.rb +44 -0
- data/lib/ably/support.rb +14 -0
- data/lib/ably/token.rb +55 -0
- data/lib/ably/version.rb +3 -0
- data/spec/acceptance/realtime_client_spec.rb +12 -0
- data/spec/acceptance/rest/auth_spec.rb +441 -0
- data/spec/acceptance/rest/base_spec.rb +113 -0
- data/spec/acceptance/rest/channel_spec.rb +68 -0
- data/spec/acceptance/rest/presence_spec.rb +22 -0
- data/spec/acceptance/rest/stats_spec.rb +57 -0
- data/spec/acceptance/rest/time_spec.rb +14 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/api_helper.rb +41 -0
- data/spec/support/test_app.rb +77 -0
- data/spec/unit/auth.rb +9 -0
- data/spec/unit/realtime_spec.rb +9 -0
- data/spec/unit/rest_spec.rb +99 -0
- data/spec/unit/token_spec.rb +90 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3b4423dc2ad92073b07e69cb72a3acbbe2d0973a
|
4
|
+
data.tar.gz: 76c5bb17b624875ec6fea9300c3778b216635266
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e107b6875dbfc0bd05ba14c48b37bb0d6fea3d514666353495ff12c822bcc62f09322a3442eed5239ccf1ac1df466e48cb59387970be373260bfb9c40f28dce2
|
7
|
+
data.tar.gz: aa9f928e0f48b74cb158d8f0b7b8d21dec755427b02bff93d8f73fd6233be0ec4f8a5926acfd7ee0de3d6e6c3aff364f005122b561d7db4ee18aa6c169934703
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Lewis Marshall
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Ably
|
2
|
+
|
3
|
+
A Ruby client library for [ably.io](https://ably.io), the real-time messaging service.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ably'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ably
|
18
|
+
|
19
|
+
## Using the Realtime API
|
20
|
+
|
21
|
+
### Subscribing to a channel
|
22
|
+
|
23
|
+
Given:
|
24
|
+
|
25
|
+
```
|
26
|
+
client = Ably::Realtime.new(api_key: "xxxxx")
|
27
|
+
|
28
|
+
channel = client.channel("test")
|
29
|
+
```
|
30
|
+
|
31
|
+
Subscribe to all events:
|
32
|
+
|
33
|
+
```
|
34
|
+
channel.subscribe do |message|
|
35
|
+
message[:name] #=> "greeting"
|
36
|
+
message[:data] #=> "Hello World!"
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
Only certain events:
|
41
|
+
|
42
|
+
```
|
43
|
+
channel.subscribe("myEvent") do |message|
|
44
|
+
message[:name] #=> "myEvent"
|
45
|
+
message[:data] #=> "myData"
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
### Publishing to a channel
|
50
|
+
|
51
|
+
```
|
52
|
+
client = Ably::Realtime.new(api_key: "xxxxx")
|
53
|
+
|
54
|
+
channel = client.channel("test")
|
55
|
+
|
56
|
+
channel.publish("greeting", "Hello World!")
|
57
|
+
```
|
58
|
+
|
59
|
+
## Using the REST API
|
60
|
+
|
61
|
+
### Publishing a message to a channel
|
62
|
+
|
63
|
+
```
|
64
|
+
client = Ably::Rest.new(api_key: "xxxxx")
|
65
|
+
|
66
|
+
channel = client.channel("test")
|
67
|
+
|
68
|
+
channel.publish("myEvent", "Hello!") #=> true
|
69
|
+
```
|
70
|
+
|
71
|
+
### Fetching a channel's history
|
72
|
+
|
73
|
+
```
|
74
|
+
client = Ably::Rest.new(api_key: "xxxxx")
|
75
|
+
|
76
|
+
channel = client.channel("test")
|
77
|
+
|
78
|
+
channel.history #=> [{:name=>"test", :data=>"payload"}]
|
79
|
+
```
|
80
|
+
|
81
|
+
### Fetching your application's stats
|
82
|
+
|
83
|
+
```
|
84
|
+
client = Ably::Rest.new(api_key: "xxxxx")
|
85
|
+
|
86
|
+
client.stats #=> [{:channels=>..., :apiRequests=>..., ...}]
|
87
|
+
```
|
88
|
+
|
89
|
+
### Fetching the Ably service time
|
90
|
+
|
91
|
+
```
|
92
|
+
client = Ably::Rest.new(api_key: "xxxxx")
|
93
|
+
|
94
|
+
client.time #=> 2013-12-12 14:23:34 +0000
|
95
|
+
```
|
96
|
+
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
1. Fork it
|
100
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
101
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
102
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
103
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/ably.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
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"
|
22
|
+
spec.add_runtime_dependency "faraday", "~> 0.9"
|
23
|
+
spec.add_runtime_dependency "json"
|
24
|
+
spec.add_runtime_dependency "websocket-driver"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "redcarpet"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
+
spec.add_development_dependency "yard"
|
31
|
+
spec.add_development_dependency "webmock"
|
32
|
+
end
|
data/lib/ably.rb
ADDED
data/lib/ably/auth.rb
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
require "json"
|
2
|
+
require "faraday"
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
require "ably/rest/middleware/external_exceptions"
|
6
|
+
require "ably/rest/middleware/parse_json"
|
7
|
+
|
8
|
+
module Ably
|
9
|
+
# Auth is responsible for authentication with {https://ably.io Ably} using basic or token authentication
|
10
|
+
#
|
11
|
+
# Find out more about Ably authentication at: http://docs.ably.io/other/authentication/
|
12
|
+
#
|
13
|
+
# @!attribute [r] client_id
|
14
|
+
# @return [String] The provided client ID, used for identifying this client for presence purposes
|
15
|
+
# @!attribute [r] current_token
|
16
|
+
# @return [Ably::Token] Current {Ably::Token} issued by this library or one of the provided callbacks used to authenticate requests
|
17
|
+
# @!attribute [r] token_id
|
18
|
+
# @return [String] Token ID provided to the {Ably::Client} constructor that is used to authenticate all requests
|
19
|
+
# @!attribute [r] api_key
|
20
|
+
# @return [String] Complete API key containing both the key ID and key secret, if present
|
21
|
+
# @!attribute [r] key_id
|
22
|
+
# @return [String] Key ID (public part of the API key), if present
|
23
|
+
# @!attribute [r] key_secret
|
24
|
+
# @return [String] Key secret (private secure part of the API key), if present
|
25
|
+
# @!attribute [r] options
|
26
|
+
# @return [Hash] {Ably::Auth} options configured for this client
|
27
|
+
|
28
|
+
class Auth
|
29
|
+
include Ably::Support
|
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 [Hash] auth_options see {Ably::Rest::Client#initialize}
|
38
|
+
# @yield [auth_options] see {Ably::Rest::Client#initialize}
|
39
|
+
def initialize(client, auth_options, &auth_block)
|
40
|
+
@client = client
|
41
|
+
@options = auth_options
|
42
|
+
@auth_callback = auth_block if block_given?
|
43
|
+
|
44
|
+
unless auth_options.kind_of?(Hash)
|
45
|
+
raise ArgumentError, "Expected auth_options to be a Hash"
|
46
|
+
end
|
47
|
+
|
48
|
+
if auth_options[:api_key] && (auth_options[:key_secret] || auth_options[:key_id])
|
49
|
+
raise ArgumentError, "api_key and key_id or key_secret are mutually exclusive. Provider either an api_key or key_id & key_secret"
|
50
|
+
end
|
51
|
+
|
52
|
+
if auth_options[:api_key]
|
53
|
+
api_key_parts = auth_options[:api_key].to_s.match(/(?<id>[\w_-]+\.[\w_-]+):(?<secret>[\w_-]+)/)
|
54
|
+
raise ArgumentError, "api_key is invalid" unless api_key_parts
|
55
|
+
auth_options[:key_id] = api_key_parts[:id]
|
56
|
+
auth_options[:key_secret] = api_key_parts[:secret]
|
57
|
+
end
|
58
|
+
|
59
|
+
if using_basic_auth? && !api_key_present?
|
60
|
+
raise ArgumentError, "api_key is missing. Either an API key, token, or token auth method must be provided"
|
61
|
+
end
|
62
|
+
|
63
|
+
if has_client_id? && !api_key_present?
|
64
|
+
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"
|
65
|
+
end
|
66
|
+
|
67
|
+
@options.freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# 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.
|
71
|
+
#
|
72
|
+
# In the event that a new token request is made, the specified options are used.
|
73
|
+
#
|
74
|
+
# @param [Hash] options the options for the token request
|
75
|
+
# @option options [String] :key_id key ID for the designated application (defaults to client key_id)
|
76
|
+
# @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
|
77
|
+
# @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
|
78
|
+
# @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.
|
79
|
+
# @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
|
80
|
+
# @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
|
81
|
+
# @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
|
82
|
+
# @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
83
|
+
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
84
|
+
# @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
|
85
|
+
# @option options [Integer] :timestamp the time of the of the request in seconds since the epoch
|
86
|
+
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
87
|
+
# @option options [Boolean] :force obtains a new token even if the current token is valid
|
88
|
+
#
|
89
|
+
# @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
|
90
|
+
# @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
|
91
|
+
# @yieldreturn [Hash] valid token request object, see {#create_token_request}
|
92
|
+
#
|
93
|
+
# @return [Ably::Token]
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# # will issue a simple token request using basic auth
|
97
|
+
# client = Ably::Rest::Client.new(api_key: 'key.id:secret')
|
98
|
+
# token = client.auth.authorise
|
99
|
+
#
|
100
|
+
# # will use token request from block to authorise if not already authorised
|
101
|
+
# token = client.auth.authorise do |options|
|
102
|
+
# # create token_request object
|
103
|
+
# token_request
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
def authorise(options = {}, &block)
|
107
|
+
if !options[:force] && current_token
|
108
|
+
return current_token unless current_token.expired?
|
109
|
+
end
|
110
|
+
|
111
|
+
@current_token = request_token(options, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Request a {Ably::Token} which can be used to make authenticated token based requests
|
115
|
+
#
|
116
|
+
# @param [Hash] options the options for the token request
|
117
|
+
# @option options [String] :key_id key ID for the designated application (defaults to client key_id)
|
118
|
+
# @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
|
119
|
+
# @option options [String] :client_id client ID identifying this connection to other clients (defaults to client client_id if configured)
|
120
|
+
# @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.
|
121
|
+
# @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
|
122
|
+
# @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
|
123
|
+
# @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
|
124
|
+
# @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
|
125
|
+
# @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
|
126
|
+
# @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
|
127
|
+
# @option options [Integer] :timestamp the time of the of the request in seconds since the epoch
|
128
|
+
# @option options [String] :nonce an unquoted, unescaped random string of at least 16 characters
|
129
|
+
#
|
130
|
+
# @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
|
131
|
+
# @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
|
132
|
+
# @yieldreturn [Hash] valid token request object, see {#create_token_request}
|
133
|
+
#
|
134
|
+
# @return [Ably::Token]
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# # simple token request using basic auth
|
138
|
+
# client = Ably::Rest::Client.new(api_key: 'key.id:secret')
|
139
|
+
# token = client.auth.request_token
|
140
|
+
#
|
141
|
+
# # token request using auth block
|
142
|
+
# token = client.auth.request_token do |options|
|
143
|
+
# # create token_request object
|
144
|
+
# token_request
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
def request_token(options = {}, &block)
|
148
|
+
token_options = self.auth_options.merge(options)
|
149
|
+
|
150
|
+
auth_url = token_options.delete(:auth_url)
|
151
|
+
token_request = if block_given?
|
152
|
+
yield(token_options)
|
153
|
+
elsif auth_callback
|
154
|
+
auth_callback.call(token_options)
|
155
|
+
elsif auth_url
|
156
|
+
token_request_from_auth_url(auth_url, token_options)
|
157
|
+
else
|
158
|
+
create_token_request(token_options)
|
159
|
+
end
|
160
|
+
|
161
|
+
response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request, send_auth_header: false)
|
162
|
+
|
163
|
+
Ably::Token.new(response.body[: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::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 [Integer] :timestamp the time of the of the request in seconds since the epoch
|
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.dup
|
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 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
|
+
Time.now
|
203
|
+
end.to_i
|
204
|
+
|
205
|
+
token_request = {
|
206
|
+
id: request_key_id,
|
207
|
+
client_id: client_id,
|
208
|
+
ttl: Token::DEFAULTS[:ttl],
|
209
|
+
timestamp: timestamp,
|
210
|
+
capability: 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
|
+
token_request[:mac] = sign_params(token_request, request_key_secret)
|
219
|
+
|
220
|
+
token_request
|
221
|
+
end
|
222
|
+
|
223
|
+
def api_key
|
224
|
+
"#{key_id}:#{key_secret}" if api_key_present?
|
225
|
+
end
|
226
|
+
|
227
|
+
def key_id
|
228
|
+
options[:key_id]
|
229
|
+
end
|
230
|
+
|
231
|
+
def key_secret
|
232
|
+
options[:key_secret]
|
233
|
+
end
|
234
|
+
|
235
|
+
# True when Basic Auth is being used to authenticate with Ably
|
236
|
+
def using_basic_auth?
|
237
|
+
!using_token_auth?
|
238
|
+
end
|
239
|
+
|
240
|
+
# True when Token Auth is being used to authenticate with Ably
|
241
|
+
def using_token_auth?
|
242
|
+
token_id || current_token || has_client_id? || token_creatable_externally?
|
243
|
+
end
|
244
|
+
|
245
|
+
def client_id
|
246
|
+
options[:client_id]
|
247
|
+
end
|
248
|
+
|
249
|
+
def token_id
|
250
|
+
options[:token_id]
|
251
|
+
end
|
252
|
+
|
253
|
+
# Auth header string used in HTTP requests to Ably
|
254
|
+
#
|
255
|
+
# @return [String] HTTP authentication value used in HTTP_AUTHORIZATION header
|
256
|
+
def auth_header
|
257
|
+
if using_token_auth?
|
258
|
+
token_auth_header
|
259
|
+
else
|
260
|
+
basic_auth_header
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
attr_reader :auth_callback
|
266
|
+
|
267
|
+
def basic_auth_header
|
268
|
+
raise InsecureRequestError, "Cannot use Basic Auth over non-TLS connections" unless client.use_tls?
|
269
|
+
"Basic #{encode64("#{api_key}")}"
|
270
|
+
end
|
271
|
+
|
272
|
+
def token_auth_header
|
273
|
+
current_token_id = if token_id
|
274
|
+
token_id
|
275
|
+
else
|
276
|
+
authorise.id
|
277
|
+
end
|
278
|
+
|
279
|
+
"Bearer #{encode64(current_token_id)}"
|
280
|
+
end
|
281
|
+
|
282
|
+
# Sign the request params using the secret
|
283
|
+
#
|
284
|
+
# @return [Hash]
|
285
|
+
def sign_params(params, secret)
|
286
|
+
text = params.values_at(
|
287
|
+
:id,
|
288
|
+
:ttl,
|
289
|
+
:capability,
|
290
|
+
:client_id,
|
291
|
+
:timestamp,
|
292
|
+
:nonce
|
293
|
+
).map { |t| "#{t}\n" }.join("")
|
294
|
+
|
295
|
+
encode64(
|
296
|
+
Digest::HMAC.digest(text, secret, Digest::SHA256)
|
297
|
+
)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Retrieve a token request from a specified URL, expects a JSON response
|
301
|
+
#
|
302
|
+
# @return [Hash]
|
303
|
+
def token_request_from_auth_url(auth_url, options = {})
|
304
|
+
uri = URI.parse(auth_url)
|
305
|
+
connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
|
306
|
+
method = options[:auth_method] || :get
|
307
|
+
|
308
|
+
connection.send(method) do |request|
|
309
|
+
request.url uri.path
|
310
|
+
request.params = options[:auth_params] || {}
|
311
|
+
request.headers = options[:auth_headers] || {}
|
312
|
+
end.body
|
313
|
+
end
|
314
|
+
|
315
|
+
# Return a Hash of connection options to initiate the Faraday::Connection with
|
316
|
+
#
|
317
|
+
# @return [Hash]
|
318
|
+
def connection_options
|
319
|
+
@connection_options ||= {
|
320
|
+
builder: middleware,
|
321
|
+
headers: {
|
322
|
+
accept: "application/json",
|
323
|
+
user_agent: user_agent
|
324
|
+
},
|
325
|
+
request: {
|
326
|
+
open_timeout: 5,
|
327
|
+
timeout: 10
|
328
|
+
}
|
329
|
+
}
|
330
|
+
end
|
331
|
+
|
332
|
+
# Return a Faraday middleware stack to initiate the Faraday::Connection with
|
333
|
+
#
|
334
|
+
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
335
|
+
def middleware
|
336
|
+
@middleware ||= Faraday::RackBuilder.new do |builder|
|
337
|
+
# Convert request params to "www-form-urlencoded"
|
338
|
+
builder.use Faraday::Request::UrlEncoded
|
339
|
+
|
340
|
+
# Parse JSON response bodies
|
341
|
+
builder.use Ably::Rest::Middleware::ParseJson
|
342
|
+
|
343
|
+
# Log HTTP requests if debug_http option set
|
344
|
+
builder.response :logger if @debug_http
|
345
|
+
|
346
|
+
# Raise exceptions if response code is invalid
|
347
|
+
builder.use Ably::Rest::Middleware::ExternalExceptions
|
348
|
+
|
349
|
+
# Set Faraday's HTTP adapter
|
350
|
+
builder.adapter Faraday.default_adapter
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def token_callback_present?
|
355
|
+
!!auth_callback
|
356
|
+
end
|
357
|
+
|
358
|
+
def token_url_present?
|
359
|
+
!!options[:auth_url]
|
360
|
+
end
|
361
|
+
|
362
|
+
def token_creatable_externally?
|
363
|
+
token_callback_present? || token_url_present?
|
364
|
+
end
|
365
|
+
|
366
|
+
def token_renewable?
|
367
|
+
use_basic_auth? || token_creatable_externally?
|
368
|
+
end
|
369
|
+
|
370
|
+
def has_client_id?
|
371
|
+
!!client_id
|
372
|
+
end
|
373
|
+
|
374
|
+
def api_key_present?
|
375
|
+
key_id && key_secret
|
376
|
+
end
|
377
|
+
|
378
|
+
private
|
379
|
+
attr_reader :client
|
380
|
+
end
|
381
|
+
end
|