internetdata 2.2.0 → 2.3.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 +4 -4
- data/README.md +17 -0
- data/lib/internetdata/api/database_v2_api.rb +5 -5
- data/lib/internetdata/api_client.rb +3 -3
- data/lib/internetdata/api_error.rb +3 -3
- data/lib/internetdata/api_model_base.rb +3 -3
- data/lib/internetdata/client.rb +5 -2
- data/lib/internetdata/configuration.rb +3 -3
- data/lib/internetdata/errors.rb +55 -13
- data/lib/internetdata/models/database.rb +8 -8
- data/lib/internetdata/models/database_checksums_response.rb +3 -3
- data/lib/internetdata/models/database_format.rb +3 -3
- data/lib/internetdata/models/database_list.rb +3 -3
- data/lib/internetdata/models/database_metadata.rb +3 -3
- data/lib/internetdata/models/database_metadata_column.rb +3 -3
- data/lib/internetdata/models/database_version.rb +3 -3
- data/lib/internetdata/models/db_checksums.rb +3 -3
- data/lib/internetdata/models/device_authorization.rb +282 -0
- data/lib/internetdata/models/download.rb +3 -3
- data/lib/internetdata/models/download_list.rb +3 -3
- data/lib/internetdata/models/error_envelope.rb +3 -3
- data/lib/internetdata/models/oauth_metadata.rb +309 -0
- data/lib/internetdata/models/standing.rb +3 -3
- data/lib/internetdata/models/token_response.rb +258 -0
- data/lib/internetdata/oauth_api.rb +166 -0
- data/lib/internetdata/transport.rb +63 -0
- data/lib/internetdata/version.rb +1 -1
- data/lib/internetdata.rb +1 -0
- metadata +5 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
#InternetData API
|
|
3
|
+
|
|
4
|
+
#The InternetData API: download the databases your organization is licensed for, and manage the account behind them. See https://docs.internetdata.io for guides and https://github.com/internetdata for the official client libraries.
|
|
5
|
+
|
|
6
|
+
The version of the OpenAPI document: 2026.09.21
|
|
7
|
+
Contact: support@internetdata.io
|
|
8
|
+
Generated by: https://openapi-generator.tech
|
|
9
|
+
Generator version: 7.25.0
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
require 'date'
|
|
14
|
+
require 'time'
|
|
15
|
+
|
|
16
|
+
module InternetData
|
|
17
|
+
class TokenResponse < ApiModelBase
|
|
18
|
+
attr_accessor :access_token
|
|
19
|
+
|
|
20
|
+
# Always `Bearer`.
|
|
21
|
+
attr_accessor :token_type
|
|
22
|
+
|
|
23
|
+
# Seconds until the access token expires.
|
|
24
|
+
attr_accessor :expires_in
|
|
25
|
+
|
|
26
|
+
# Always returned. A refresh consumes the token it presents, so keep this one.
|
|
27
|
+
attr_accessor :refresh_token
|
|
28
|
+
|
|
29
|
+
# What was actually granted, which may be narrower than what was asked for.
|
|
30
|
+
attr_accessor :scope
|
|
31
|
+
|
|
32
|
+
# Not part of OAuth. The ID of the API key the person picked when they approved, returned by every grant while this authorization may still read that key back. Absent when no key was picked, or when the person's role no longer allows reading keys back.
|
|
33
|
+
attr_accessor :apikey_id
|
|
34
|
+
|
|
35
|
+
# Not part of OAuth. The API key itself, so a device ends up holding an ordinary key. Returned by the device code and authorization code grants only, never by a refresh, and only alongside `mslm:apikey_id`. Absent when that key's secret cannot be read back, which is the case for a key created before keys could be shown again in the console; a rotated key can be.
|
|
36
|
+
attr_accessor :apikey
|
|
37
|
+
|
|
38
|
+
# Attribute mapping from ruby-style variable name to JSON key.
|
|
39
|
+
def self.attribute_map
|
|
40
|
+
{
|
|
41
|
+
:'access_token' => :'access_token',
|
|
42
|
+
:'token_type' => :'token_type',
|
|
43
|
+
:'expires_in' => :'expires_in',
|
|
44
|
+
:'refresh_token' => :'refresh_token',
|
|
45
|
+
:'scope' => :'scope',
|
|
46
|
+
:'apikey_id' => :'mslm:apikey_id',
|
|
47
|
+
:'apikey' => :'mslm:apikey'
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns attribute mapping this model knows about
|
|
52
|
+
def self.acceptable_attribute_map
|
|
53
|
+
attribute_map
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns all the JSON keys this model knows about
|
|
57
|
+
def self.acceptable_attributes
|
|
58
|
+
acceptable_attribute_map.values
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Attribute type mapping.
|
|
62
|
+
def self.openapi_types
|
|
63
|
+
{
|
|
64
|
+
:'access_token' => :'String',
|
|
65
|
+
:'token_type' => :'String',
|
|
66
|
+
:'expires_in' => :'Integer',
|
|
67
|
+
:'refresh_token' => :'String',
|
|
68
|
+
:'scope' => :'String',
|
|
69
|
+
:'apikey_id' => :'String',
|
|
70
|
+
:'apikey' => :'String'
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# List of attributes with nullable: true
|
|
75
|
+
def self.openapi_nullable
|
|
76
|
+
Set.new([
|
|
77
|
+
])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Initializes the object
|
|
81
|
+
# @param [Hash] attributes Model attributes in the form of hash
|
|
82
|
+
def initialize(attributes = {})
|
|
83
|
+
if (!attributes.is_a?(Hash))
|
|
84
|
+
fail ArgumentError, "The input argument (attributes) must be a hash in `InternetData::TokenResponse` initialize method"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# check to see if the attribute exists and convert string to symbol for hash key
|
|
88
|
+
acceptable_attribute_map = self.class.acceptable_attribute_map
|
|
89
|
+
attributes = attributes.each_with_object({}) { |(k, v), h|
|
|
90
|
+
if (!acceptable_attribute_map.key?(k.to_sym))
|
|
91
|
+
fail ArgumentError, "`#{k}` is not a valid attribute in `InternetData::TokenResponse`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
|
|
92
|
+
end
|
|
93
|
+
h[k.to_sym] = v
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if attributes.key?(:'access_token')
|
|
97
|
+
self.access_token = attributes[:'access_token']
|
|
98
|
+
else
|
|
99
|
+
self.access_token = nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if attributes.key?(:'token_type')
|
|
103
|
+
self.token_type = attributes[:'token_type']
|
|
104
|
+
else
|
|
105
|
+
self.token_type = nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if attributes.key?(:'expires_in')
|
|
109
|
+
self.expires_in = attributes[:'expires_in']
|
|
110
|
+
else
|
|
111
|
+
self.expires_in = nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if attributes.key?(:'refresh_token')
|
|
115
|
+
self.refresh_token = attributes[:'refresh_token']
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if attributes.key?(:'scope')
|
|
119
|
+
self.scope = attributes[:'scope']
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if attributes.key?(:'apikey_id')
|
|
123
|
+
self.apikey_id = attributes[:'apikey_id']
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if attributes.key?(:'apikey')
|
|
127
|
+
self.apikey = attributes[:'apikey']
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Show invalid properties with the reasons. Usually used together with valid?
|
|
132
|
+
# @return Array for valid properties with the reasons
|
|
133
|
+
def list_invalid_properties
|
|
134
|
+
warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
|
|
135
|
+
invalid_properties = Array.new
|
|
136
|
+
if @access_token.nil?
|
|
137
|
+
invalid_properties.push('invalid value for "access_token", access_token cannot be nil.')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if @token_type.nil?
|
|
141
|
+
invalid_properties.push('invalid value for "token_type", token_type cannot be nil.')
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if @expires_in.nil?
|
|
145
|
+
invalid_properties.push('invalid value for "expires_in", expires_in cannot be nil.')
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
invalid_properties
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Check to see if the all the properties in the model are valid
|
|
152
|
+
# @return true if the model is valid
|
|
153
|
+
def valid?
|
|
154
|
+
warn '[DEPRECATED] the `valid?` method is obsolete'
|
|
155
|
+
return false if @access_token.nil?
|
|
156
|
+
return false if @token_type.nil?
|
|
157
|
+
return false if @expires_in.nil?
|
|
158
|
+
true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Custom attribute writer method with validation
|
|
162
|
+
# @param [Object] access_token Value to be assigned
|
|
163
|
+
def access_token=(access_token)
|
|
164
|
+
if access_token.nil?
|
|
165
|
+
fail ArgumentError, 'access_token cannot be nil'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
@access_token = access_token
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Custom attribute writer method with validation
|
|
172
|
+
# @param [Object] token_type Value to be assigned
|
|
173
|
+
def token_type=(token_type)
|
|
174
|
+
if token_type.nil?
|
|
175
|
+
fail ArgumentError, 'token_type cannot be nil'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
@token_type = token_type
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Custom attribute writer method with validation
|
|
182
|
+
# @param [Object] expires_in Value to be assigned
|
|
183
|
+
def expires_in=(expires_in)
|
|
184
|
+
if expires_in.nil?
|
|
185
|
+
fail ArgumentError, 'expires_in cannot be nil'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
@expires_in = expires_in
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Checks equality by comparing each attribute.
|
|
192
|
+
# @param [Object] Object to be compared
|
|
193
|
+
def ==(o)
|
|
194
|
+
return true if self.equal?(o)
|
|
195
|
+
self.class == o.class &&
|
|
196
|
+
access_token == o.access_token &&
|
|
197
|
+
token_type == o.token_type &&
|
|
198
|
+
expires_in == o.expires_in &&
|
|
199
|
+
refresh_token == o.refresh_token &&
|
|
200
|
+
scope == o.scope &&
|
|
201
|
+
apikey_id == o.apikey_id &&
|
|
202
|
+
apikey == o.apikey
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @see the `==` method
|
|
206
|
+
# @param [Object] Object to be compared
|
|
207
|
+
def eql?(o)
|
|
208
|
+
self == o
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Calculates hash code according to all attributes.
|
|
212
|
+
# @return [Integer] Hash code
|
|
213
|
+
def hash
|
|
214
|
+
[access_token, token_type, expires_in, refresh_token, scope, apikey_id, apikey].hash
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Builds the object from hash
|
|
218
|
+
# @param [Hash] attributes Model attributes in the form of hash
|
|
219
|
+
# @return [Object] Returns the model itself
|
|
220
|
+
def self.build_from_hash(attributes)
|
|
221
|
+
return nil unless attributes.is_a?(Hash)
|
|
222
|
+
attributes = attributes.transform_keys(&:to_sym)
|
|
223
|
+
transformed_hash = {}
|
|
224
|
+
openapi_types.each_pair do |key, type|
|
|
225
|
+
if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
|
|
226
|
+
transformed_hash["#{key}"] = nil
|
|
227
|
+
elsif type =~ /\AArray<(.*)>/i
|
|
228
|
+
# check to ensure the input is an array given that the attribute
|
|
229
|
+
# is documented as an array but the input is not
|
|
230
|
+
if attributes[attribute_map[key]].is_a?(Array)
|
|
231
|
+
transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
|
|
232
|
+
end
|
|
233
|
+
elsif !attributes[attribute_map[key]].nil?
|
|
234
|
+
transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
new(transformed_hash)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Returns the object in the form of hash
|
|
241
|
+
# @return [Hash] Returns the object in the form of hash
|
|
242
|
+
def to_hash
|
|
243
|
+
hash = {}
|
|
244
|
+
self.class.attribute_map.each_pair do |attr, param|
|
|
245
|
+
value = self.send(attr)
|
|
246
|
+
if value.nil?
|
|
247
|
+
is_nullable = self.class.openapi_nullable.include?(attr)
|
|
248
|
+
next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
hash[param] = _to_hash(value)
|
|
252
|
+
end
|
|
253
|
+
hash
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InternetData
|
|
4
|
+
# Signing a person in with OAuth, reached as `client.oauth`.
|
|
5
|
+
#
|
|
6
|
+
# A program on the person's own machine starts a device sign-in, shows them a
|
|
7
|
+
# code to approve in their browser, and waits for the tokens, which can carry
|
|
8
|
+
# one of their API keys so nobody pastes a key by hand. Only a registered
|
|
9
|
+
# client ID works; they are issued on request from support@internetdata.io.
|
|
10
|
+
#
|
|
11
|
+
# No request made here carries the API key this client was built with, and a
|
|
12
|
+
# client built without one works exactly the same. Every method takes
|
|
13
|
+
# `timeout:`, seconds per attempt, for that call alone.
|
|
14
|
+
class OauthApi
|
|
15
|
+
METADATA_PATH = '/.well-known/oauth-authorization-server'
|
|
16
|
+
DEVICE_AUTHORIZATION_PATH = '/oauth/device_authorization'
|
|
17
|
+
TOKEN_PATH = '/oauth/token'
|
|
18
|
+
REVOKE_PATH = '/oauth/revoke'
|
|
19
|
+
DEVICE_CODE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code'
|
|
20
|
+
|
|
21
|
+
# The members a 2xx must carry for its type to mean anything.
|
|
22
|
+
REQUIRED = {
|
|
23
|
+
OauthMetadata => %i[issuer authorization_endpoint token_endpoint],
|
|
24
|
+
DeviceAuthorization => %i[device_code user_code verification_uri expires_in interval],
|
|
25
|
+
TokenResponse => %i[access_token token_type expires_in],
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
def initialize(transport, retries:)
|
|
29
|
+
@transport = transport
|
|
30
|
+
@retries = retries
|
|
31
|
+
# The poll's wait and its monotonic clock, which a test replaces together.
|
|
32
|
+
@wait = ->(seconds) { sleep(seconds) }
|
|
33
|
+
@now = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The authorization server's discovery document.
|
|
37
|
+
#
|
|
38
|
+
# @return [OauthMetadata]
|
|
39
|
+
def metadata(timeout: nil)
|
|
40
|
+
Retries.with_retries(@retries) do
|
|
41
|
+
decode(OauthMetadata, @transport.oauth_request(:GET, METADATA_PATH, timeout: timeout).run)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Start a device sign-in: show the person `user_code` and `verification_uri`,
|
|
46
|
+
# then {#poll_device_token}.
|
|
47
|
+
#
|
|
48
|
+
# @param scope [String, nil] space-delimited, sent as given; the server grants
|
|
49
|
+
# what this client may ask for and silently drops the rest.
|
|
50
|
+
# @param resource [String, nil] the API the tokens are for.
|
|
51
|
+
# @return [DeviceAuthorization]
|
|
52
|
+
def device_authorization(client_id, scope: nil, resource: nil, timeout: nil)
|
|
53
|
+
form = { 'client_id' => client_id, 'scope' => scope, 'resource' => resource }.compact
|
|
54
|
+
Retries.with_retries(@retries) do
|
|
55
|
+
request = @transport.oauth_request(:POST, DEVICE_AUTHORIZATION_PATH, form: form, timeout: timeout)
|
|
56
|
+
decode(DeviceAuthorization, request.run)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Exchange a device code for tokens, once. Until the person approves this
|
|
61
|
+
# raises {OauthRequestError} coded `authorization_pending`;
|
|
62
|
+
# {#poll_device_token} is the loop that waits for them.
|
|
63
|
+
#
|
|
64
|
+
# Never retried: the server spends the code when it answers, so a retry after
|
|
65
|
+
# a lost success could only fail and lose the tokens.
|
|
66
|
+
#
|
|
67
|
+
# @return [TokenResponse] with `apikey_id` and `apikey` when the person picked a key.
|
|
68
|
+
def exchange_device_code(client_id, device_code, timeout: nil)
|
|
69
|
+
form = { 'grant_type' => DEVICE_CODE_GRANT, 'device_code' => device_code, 'client_id' => client_id }
|
|
70
|
+
exchange(form, timeout)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Exchange a refresh token for a new pair. The token presented is spent, so
|
|
74
|
+
# keep the `refresh_token` this returns. Never retried, for the same reason as
|
|
75
|
+
# {#exchange_device_code}.
|
|
76
|
+
#
|
|
77
|
+
# @return [TokenResponse] which may name the key in `apikey_id`, but never carries `apikey`.
|
|
78
|
+
def exchange_refresh_token(client_id, refresh_token, timeout: nil)
|
|
79
|
+
form = { 'grant_type' => 'refresh_token', 'refresh_token' => refresh_token, 'client_id' => client_id }
|
|
80
|
+
exchange(form, timeout)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Revoke an access or refresh token. A refresh token ends the whole grant and
|
|
84
|
+
# every token it issued, which is how a machine signs out.
|
|
85
|
+
#
|
|
86
|
+
# @return [nil]
|
|
87
|
+
def revoke(client_id, token, timeout: nil)
|
|
88
|
+
form = { 'token' => token, 'client_id' => client_id }
|
|
89
|
+
Retries.with_retries(@retries) do
|
|
90
|
+
request = @transport.oauth_request(:POST, REVOKE_PATH, form: form, timeout: timeout)
|
|
91
|
+
Transport.oauth_success!(request.run)
|
|
92
|
+
end
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Wait for the person to approve a device sign-in, and return its tokens.
|
|
97
|
+
#
|
|
98
|
+
# Waits `device.interval` seconds before EVERY exchange, the first included,
|
|
99
|
+
# and five seconds longer for good each time the server answers `slow_down`.
|
|
100
|
+
# Raises {OauthAccessDeniedError} when the person refuses and
|
|
101
|
+
# {OauthExpiredTokenError} when the code expires, including locally, with no
|
|
102
|
+
# status, once `device.expires_in` seconds have passed since this call. Any
|
|
103
|
+
# other failure, a timeout or an outage included, ends the wait unchanged;
|
|
104
|
+
# calling again with the same device is safe until the code expires.
|
|
105
|
+
#
|
|
106
|
+
# There is no way to cancel it from outside: it blocks until one of those
|
|
107
|
+
# outcomes, so run it where blocking is acceptable.
|
|
108
|
+
#
|
|
109
|
+
# @param timeout [Numeric, nil] bounds each exchange, never the whole wait.
|
|
110
|
+
# @return [TokenResponse]
|
|
111
|
+
def poll_device_token(client_id, device, timeout: nil)
|
|
112
|
+
interval = device.interval >= 1 ? device.interval : 5
|
|
113
|
+
deadline = @now.call + device.expires_in
|
|
114
|
+
loop do
|
|
115
|
+
@wait.call(interval)
|
|
116
|
+
raise OauthExpiredTokenError, 'expired_token' if @now.call >= deadline
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
return exchange_device_code(client_id, device.device_code, timeout: timeout)
|
|
120
|
+
rescue OauthRequestError => e
|
|
121
|
+
case e.error_code
|
|
122
|
+
when 'slow_down' then interval += 5
|
|
123
|
+
when 'authorization_pending' then next
|
|
124
|
+
else raise
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def exchange(form, timeout)
|
|
133
|
+
decode(TokenResponse, @transport.oauth_request(:POST, TOKEN_PATH, form: form, timeout: timeout).run)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Only the members the type declares are read, each checked against the type
|
|
137
|
+
# the spec gives it, so an absent member stays nil and an empty `scope` stays
|
|
138
|
+
# an empty string. A 2xx that is not the type is the server's fault.
|
|
139
|
+
def decode(type, response)
|
|
140
|
+
body = Transport.oauth_object(response)
|
|
141
|
+
type.attribute_map.each do |member, wire|
|
|
142
|
+
value = body[wire.to_s]
|
|
143
|
+
if value.nil?
|
|
144
|
+
next unless REQUIRED.fetch(type).include?(member)
|
|
145
|
+
|
|
146
|
+
raise Error.new(:server_error, "the answer carried no #{wire}", status: response.code)
|
|
147
|
+
end
|
|
148
|
+
next if typed?(type.openapi_types.fetch(member), value)
|
|
149
|
+
|
|
150
|
+
raise Error.new(:server_error, "the answer's #{wire} is not a #{type.openapi_types[member]}",
|
|
151
|
+
status: response.code)
|
|
152
|
+
end
|
|
153
|
+
type.build_from_hash(body)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def typed?(type, value)
|
|
157
|
+
case type
|
|
158
|
+
when :String then value.is_a?(String)
|
|
159
|
+
when :Integer then value.is_a?(Integer)
|
|
160
|
+
when :Boolean then [true, false].include?(value)
|
|
161
|
+
when :'Array<String>' then value.is_a?(Array) && value.all?(String)
|
|
162
|
+
else raise ArgumentError, "no check for a member typed #{type}"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
3
4
|
require 'uri'
|
|
4
5
|
|
|
5
6
|
module InternetData
|
|
@@ -87,5 +88,67 @@ module InternetData
|
|
|
87
88
|
options[:cainfo] = @config.ssl_ca_cert if @config.ssl_ca_cert
|
|
88
89
|
Typhoeus::Request.new(url, options)
|
|
89
90
|
end
|
|
91
|
+
|
|
92
|
+
# A request to the authorization server, which carries NO credential whatever
|
|
93
|
+
# this client was built with: these endpoints have no use for the API key, and
|
|
94
|
+
# on the token endpoint an `Authorization` header reads as client
|
|
95
|
+
# authentication, which a public client does not have.
|
|
96
|
+
#
|
|
97
|
+
# The form is encoded here rather than by curl, so a `+` in a value leaves as
|
|
98
|
+
# `%2B` and never arrives as a space.
|
|
99
|
+
def oauth_request(http_method, path, form: nil, timeout: nil)
|
|
100
|
+
headers = { 'Accept' => 'application/json' }
|
|
101
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded' unless form.nil?
|
|
102
|
+
request = build_request(http_method, path, header_params: headers, auth_names: [], timeout: timeout)
|
|
103
|
+
request.options[:body] = URI.encode_www_form(form) unless form.nil?
|
|
104
|
+
request
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The JSON object a 2xx OAuth answer carries. A refusal the authorization
|
|
108
|
+
# server words as an RFC 6749 error raises that; anything else that is not a
|
|
109
|
+
# 2xx raises the ordinary error its status maps to.
|
|
110
|
+
def self.oauth_object(response)
|
|
111
|
+
oauth_success!(response)
|
|
112
|
+
parse_object(response)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.oauth_success!(response)
|
|
116
|
+
raise Error.from_transport(response) if transport_failure?(response)
|
|
117
|
+
return if response.success?
|
|
118
|
+
|
|
119
|
+
raise oauth_refusal(response) || Error.from_status(response.code, response.headers, response.body)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Only a 4xx whose body is a JSON object with a STRING `error` is the
|
|
123
|
+
# authorization server's own refusal. A 5xx is an outage whatever its body
|
|
124
|
+
# says, and a gateway's page names no OAuth code at all.
|
|
125
|
+
def self.oauth_refusal(response)
|
|
126
|
+
return nil unless (400..499).cover?(response.code)
|
|
127
|
+
|
|
128
|
+
body = JSON.parse(response.body.to_s)
|
|
129
|
+
return nil unless body.is_a?(Hash) && body['error'].is_a?(String)
|
|
130
|
+
|
|
131
|
+
description = body['error_description'].is_a?(String) ? body['error_description'] : nil
|
|
132
|
+
OauthRequestError.for_code(body['error'], description, status: response.code, headers: response.headers)
|
|
133
|
+
rescue JSON::ParserError
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.transport_failure?(response)
|
|
138
|
+
response.timed_out? || response.code.to_i.zero?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.parse_object(response)
|
|
142
|
+
body = JSON.parse(response.body.to_s)
|
|
143
|
+
return body if body.is_a?(Hash)
|
|
144
|
+
|
|
145
|
+
raise Error.new(:server_error, 'the API answered with something other than an object',
|
|
146
|
+
status: response.code)
|
|
147
|
+
rescue JSON::ParserError => e
|
|
148
|
+
raise Error.new(:server_error, "could not parse the response body: #{e.message}",
|
|
149
|
+
status: response.code)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private_class_method :transport_failure?, :parse_object, :oauth_refusal
|
|
90
153
|
end
|
|
91
154
|
end
|
data/lib/internetdata/version.rb
CHANGED
data/lib/internetdata.rb
CHANGED
|
@@ -16,6 +16,7 @@ require 'internetdata/errors'
|
|
|
16
16
|
require 'internetdata/retries'
|
|
17
17
|
require 'internetdata/transport'
|
|
18
18
|
require 'internetdata/database_api'
|
|
19
|
+
require 'internetdata/oauth_api'
|
|
19
20
|
require 'internetdata/client'
|
|
20
21
|
|
|
21
22
|
# The official Ruby client library for the InternetData API.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: internetdata
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mslm Dev
|
|
@@ -50,10 +50,14 @@ files:
|
|
|
50
50
|
- lib/internetdata/models/database_metadata_column.rb
|
|
51
51
|
- lib/internetdata/models/database_version.rb
|
|
52
52
|
- lib/internetdata/models/db_checksums.rb
|
|
53
|
+
- lib/internetdata/models/device_authorization.rb
|
|
53
54
|
- lib/internetdata/models/download.rb
|
|
54
55
|
- lib/internetdata/models/download_list.rb
|
|
55
56
|
- lib/internetdata/models/error_envelope.rb
|
|
57
|
+
- lib/internetdata/models/oauth_metadata.rb
|
|
56
58
|
- lib/internetdata/models/standing.rb
|
|
59
|
+
- lib/internetdata/models/token_response.rb
|
|
60
|
+
- lib/internetdata/oauth_api.rb
|
|
57
61
|
- lib/internetdata/retries.rb
|
|
58
62
|
- lib/internetdata/transport.rb
|
|
59
63
|
- lib/internetdata/version.rb
|