cure-google-api-client 0.8.7.1
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/CHANGELOG.md +178 -0
- data/Gemfile +9 -0
- data/LICENSE +202 -0
- data/README.md +218 -0
- data/Rakefile +41 -0
- data/google-api-client.gemspec +43 -0
- data/lib/cacerts.pem +2183 -0
- data/lib/compat/multi_json.rb +19 -0
- data/lib/google/api_client.rb +750 -0
- data/lib/google/api_client/auth/compute_service_account.rb +28 -0
- data/lib/google/api_client/auth/file_storage.rb +59 -0
- data/lib/google/api_client/auth/installed_app.rb +126 -0
- data/lib/google/api_client/auth/jwt_asserter.rb +126 -0
- data/lib/google/api_client/auth/key_utils.rb +93 -0
- data/lib/google/api_client/auth/pkcs12.rb +41 -0
- data/lib/google/api_client/auth/storage.rb +102 -0
- data/lib/google/api_client/auth/storages/file_store.rb +58 -0
- data/lib/google/api_client/auth/storages/redis_store.rb +54 -0
- data/lib/google/api_client/batch.rb +326 -0
- data/lib/google/api_client/charset.rb +33 -0
- data/lib/google/api_client/client_secrets.rb +179 -0
- data/lib/google/api_client/discovery.rb +19 -0
- data/lib/google/api_client/discovery/api.rb +310 -0
- data/lib/google/api_client/discovery/media.rb +77 -0
- data/lib/google/api_client/discovery/method.rb +363 -0
- data/lib/google/api_client/discovery/resource.rb +156 -0
- data/lib/google/api_client/discovery/schema.rb +117 -0
- data/lib/google/api_client/environment.rb +42 -0
- data/lib/google/api_client/errors.rb +65 -0
- data/lib/google/api_client/gzip.rb +28 -0
- data/lib/google/api_client/logging.rb +32 -0
- data/lib/google/api_client/media.rb +259 -0
- data/lib/google/api_client/railtie.rb +18 -0
- data/lib/google/api_client/reference.rb +27 -0
- data/lib/google/api_client/request.rb +350 -0
- data/lib/google/api_client/result.rb +255 -0
- data/lib/google/api_client/service.rb +233 -0
- data/lib/google/api_client/service/batch.rb +110 -0
- data/lib/google/api_client/service/request.rb +144 -0
- data/lib/google/api_client/service/resource.rb +40 -0
- data/lib/google/api_client/service/result.rb +162 -0
- data/lib/google/api_client/service/simple_file_store.rb +151 -0
- data/lib/google/api_client/service/stub_generator.rb +61 -0
- data/lib/google/api_client/service_account.rb +21 -0
- data/lib/google/api_client/version.rb +26 -0
- data/spec/google/api_client/auth/storage_spec.rb +122 -0
- data/spec/google/api_client/auth/storages/file_store_spec.rb +40 -0
- data/spec/google/api_client/auth/storages/redis_store_spec.rb +70 -0
- data/spec/google/api_client/batch_spec.rb +248 -0
- data/spec/google/api_client/client_secrets_spec.rb +53 -0
- data/spec/google/api_client/discovery_spec.rb +708 -0
- data/spec/google/api_client/gzip_spec.rb +98 -0
- data/spec/google/api_client/media_spec.rb +178 -0
- data/spec/google/api_client/request_spec.rb +29 -0
- data/spec/google/api_client/result_spec.rb +207 -0
- data/spec/google/api_client/service_account_spec.rb +169 -0
- data/spec/google/api_client/service_spec.rb +618 -0
- data/spec/google/api_client/simple_file_store_spec.rb +133 -0
- data/spec/google/api_client_spec.rb +352 -0
- data/spec/spec_helper.rb +66 -0
- metadata +339 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
if !MultiJson.respond_to?(:load) || [
|
4
|
+
Kernel,
|
5
|
+
defined?(ActiveSupport::Dependencies::Loadable) && ActiveSupport::Dependencies::Loadable
|
6
|
+
].compact.include?(MultiJson.method(:load).owner)
|
7
|
+
module MultiJson
|
8
|
+
class <<self
|
9
|
+
alias :load :decode
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
if !MultiJson.respond_to?(:dump)
|
14
|
+
module MultiJson
|
15
|
+
class <<self
|
16
|
+
alias :dump :encode
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,750 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require 'faraday'
|
17
|
+
require 'multi_json'
|
18
|
+
require 'compat/multi_json'
|
19
|
+
require 'stringio'
|
20
|
+
require 'retriable'
|
21
|
+
|
22
|
+
require 'google/api_client/version'
|
23
|
+
require 'google/api_client/logging'
|
24
|
+
require 'google/api_client/errors'
|
25
|
+
require 'google/api_client/environment'
|
26
|
+
require 'google/api_client/discovery'
|
27
|
+
require 'google/api_client/request'
|
28
|
+
require 'google/api_client/reference'
|
29
|
+
require 'google/api_client/result'
|
30
|
+
require 'google/api_client/media'
|
31
|
+
require 'google/api_client/service_account'
|
32
|
+
require 'google/api_client/batch'
|
33
|
+
require 'google/api_client/gzip'
|
34
|
+
require 'google/api_client/charset'
|
35
|
+
require 'google/api_client/client_secrets'
|
36
|
+
require 'google/api_client/railtie' if defined?(Rails)
|
37
|
+
|
38
|
+
module Google
|
39
|
+
|
40
|
+
##
|
41
|
+
# This class manages APIs communication.
|
42
|
+
class APIClient
|
43
|
+
include Google::APIClient::Logging
|
44
|
+
|
45
|
+
##
|
46
|
+
# Creates a new Google API client.
|
47
|
+
#
|
48
|
+
# @param [Hash] options The configuration parameters for the client.
|
49
|
+
# @option options [Symbol, #generate_authenticated_request] :authorization
|
50
|
+
# (:oauth_1)
|
51
|
+
# The authorization mechanism used by the client. The following
|
52
|
+
# mechanisms are supported out-of-the-box:
|
53
|
+
# <ul>
|
54
|
+
# <li><code>:two_legged_oauth_1</code></li>
|
55
|
+
# <li><code>:oauth_1</code></li>
|
56
|
+
# <li><code>:oauth_2</code></li>
|
57
|
+
# <li><code>:google_app_default</code></li>
|
58
|
+
# </ul>
|
59
|
+
# @option options [Boolean] :auto_refresh_token (true)
|
60
|
+
# The setting that controls whether or not the api client attempts to
|
61
|
+
# refresh authorization when a 401 is hit in #execute. If the token does
|
62
|
+
# not support it, this option is ignored.
|
63
|
+
# @option options [String] :application_name
|
64
|
+
# The name of the application using the client.
|
65
|
+
# @option options [String | Array | nil] :scope
|
66
|
+
# The scope(s) used when using google application default credentials
|
67
|
+
# @option options [String] :application_version
|
68
|
+
# The version number of the application using the client.
|
69
|
+
# @option options [String] :user_agent
|
70
|
+
# ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}")
|
71
|
+
# The user agent used by the client. Most developers will want to
|
72
|
+
# leave this value alone and use the `:application_name` option instead.
|
73
|
+
# @option options [String] :host ("www.googleapis.com")
|
74
|
+
# The API hostname used by the client. This rarely needs to be changed.
|
75
|
+
# @option options [String] :port (443)
|
76
|
+
# The port number used by the client. This rarely needs to be changed.
|
77
|
+
# @option options [String] :discovery_path ("/discovery/v1")
|
78
|
+
# The discovery base path. This rarely needs to be changed.
|
79
|
+
# @option options [String] :ca_file
|
80
|
+
# Optional set of root certificates to use when validating SSL connections.
|
81
|
+
# By default, a bundled set of trusted roots will be used.
|
82
|
+
# @options options[Hash] :force_encoding
|
83
|
+
# Experimental option. True if response body should be force encoded into the charset
|
84
|
+
# specified in the Content-Type header. Mostly intended for compressed content.
|
85
|
+
# @options options[Hash] :faraday_options
|
86
|
+
# Pass through of options to set on the Faraday connection
|
87
|
+
def initialize(options={})
|
88
|
+
logger.debug { "#{self.class} - Initializing client with options #{options}" }
|
89
|
+
|
90
|
+
# Normalize key to String to allow indifferent access.
|
91
|
+
options = options.inject({}) do |accu, (key, value)|
|
92
|
+
accu[key.to_sym] = value
|
93
|
+
accu
|
94
|
+
end
|
95
|
+
# Almost all API usage will have a host of 'www.googleapis.com'.
|
96
|
+
self.host = options[:host] || 'www.googleapis.com'
|
97
|
+
self.port = options[:port] || 443
|
98
|
+
self.discovery_path = options[:discovery_path] || '/discovery/v1'
|
99
|
+
|
100
|
+
# Most developers will want to leave this value alone and use the
|
101
|
+
# application_name option.
|
102
|
+
if options[:application_name]
|
103
|
+
app_name = options[:application_name]
|
104
|
+
app_version = options[:application_version]
|
105
|
+
application_string = "#{app_name}/#{app_version || '0.0.0'}"
|
106
|
+
else
|
107
|
+
logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
|
108
|
+
end
|
109
|
+
|
110
|
+
proxy = options[:proxy] || Object::ENV["http_proxy"]
|
111
|
+
|
112
|
+
self.user_agent = options[:user_agent] || (
|
113
|
+
"#{application_string} " +
|
114
|
+
"google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION}".strip + " (gzip)"
|
115
|
+
).strip
|
116
|
+
# The writer method understands a few Symbols and will generate useful
|
117
|
+
# default authentication mechanisms.
|
118
|
+
self.authorization =
|
119
|
+
options.key?(:authorization) ? options[:authorization] : :oauth_2
|
120
|
+
if !options['scope'].nil? and self.authorization.respond_to?(:scope=)
|
121
|
+
self.authorization.scope = options['scope']
|
122
|
+
end
|
123
|
+
self.auto_refresh_token = options.fetch(:auto_refresh_token) { true }
|
124
|
+
self.key = options[:key]
|
125
|
+
self.user_ip = options[:user_ip]
|
126
|
+
self.retries = options.fetch(:retries) { 0 }
|
127
|
+
self.expired_auth_retry = options.fetch(:expired_auth_retry) { true }
|
128
|
+
@discovery_uris = {}
|
129
|
+
@discovery_documents = {}
|
130
|
+
@discovered_apis = {}
|
131
|
+
ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__)
|
132
|
+
self.connection = Faraday.new do |faraday|
|
133
|
+
faraday.response :charset if options[:force_encoding]
|
134
|
+
faraday.response :gzip
|
135
|
+
faraday.options.params_encoder = Faraday::FlatParamsEncoder
|
136
|
+
faraday.ssl.ca_file = ca_file
|
137
|
+
faraday.ssl.verify = true
|
138
|
+
faraday.proxy proxy
|
139
|
+
faraday.adapter Faraday.default_adapter
|
140
|
+
if options[:faraday_option].is_a?(Hash)
|
141
|
+
options[:faraday_option].each_pair do |option, value|
|
142
|
+
faraday.options.send("#{option}=", value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
return self
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Returns the authorization mechanism used by the client.
|
151
|
+
#
|
152
|
+
# @return [#generate_authenticated_request] The authorization mechanism.
|
153
|
+
attr_reader :authorization
|
154
|
+
|
155
|
+
##
|
156
|
+
# Sets the authorization mechanism used by the client.
|
157
|
+
#
|
158
|
+
# @param [#generate_authenticated_request] new_authorization
|
159
|
+
# The new authorization mechanism.
|
160
|
+
def authorization=(new_authorization)
|
161
|
+
case new_authorization
|
162
|
+
when :oauth_1, :oauth
|
163
|
+
require 'signet/oauth_1/client'
|
164
|
+
# NOTE: Do not rely on this default value, as it may change
|
165
|
+
new_authorization = Signet::OAuth1::Client.new(
|
166
|
+
:temporary_credential_uri =>
|
167
|
+
'https://www.google.com/accounts/OAuthGetRequestToken',
|
168
|
+
:authorization_uri =>
|
169
|
+
'https://www.google.com/accounts/OAuthAuthorizeToken',
|
170
|
+
:token_credential_uri =>
|
171
|
+
'https://www.google.com/accounts/OAuthGetAccessToken',
|
172
|
+
:client_credential_key => 'anonymous',
|
173
|
+
:client_credential_secret => 'anonymous'
|
174
|
+
)
|
175
|
+
when :two_legged_oauth_1, :two_legged_oauth
|
176
|
+
require 'signet/oauth_1/client'
|
177
|
+
# NOTE: Do not rely on this default value, as it may change
|
178
|
+
new_authorization = Signet::OAuth1::Client.new(
|
179
|
+
:client_credential_key => nil,
|
180
|
+
:client_credential_secret => nil,
|
181
|
+
:two_legged => true
|
182
|
+
)
|
183
|
+
when :google_app_default
|
184
|
+
require 'googleauth'
|
185
|
+
new_authorization = Google::Auth.get_application_default
|
186
|
+
|
187
|
+
when :oauth_2
|
188
|
+
require 'signet/oauth_2/client'
|
189
|
+
# NOTE: Do not rely on this default value, as it may change
|
190
|
+
new_authorization = Signet::OAuth2::Client.new(
|
191
|
+
:authorization_uri =>
|
192
|
+
'https://accounts.google.com/o/oauth2/auth',
|
193
|
+
:token_credential_uri =>
|
194
|
+
'https://accounts.google.com/o/oauth2/token'
|
195
|
+
)
|
196
|
+
when nil
|
197
|
+
# No authorization mechanism
|
198
|
+
else
|
199
|
+
if !new_authorization.respond_to?(:generate_authenticated_request)
|
200
|
+
raise TypeError,
|
201
|
+
'Expected authorization mechanism to respond to ' +
|
202
|
+
'#generate_authenticated_request.'
|
203
|
+
end
|
204
|
+
end
|
205
|
+
@authorization = new_authorization
|
206
|
+
return @authorization
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Default Faraday/HTTP connection.
|
211
|
+
#
|
212
|
+
# @return [Faraday::Connection]
|
213
|
+
attr_accessor :connection
|
214
|
+
|
215
|
+
##
|
216
|
+
# The setting that controls whether or not the api client attempts to
|
217
|
+
# refresh authorization when a 401 is hit in #execute.
|
218
|
+
#
|
219
|
+
# @return [Boolean]
|
220
|
+
attr_accessor :auto_refresh_token
|
221
|
+
|
222
|
+
##
|
223
|
+
# The application's API key issued by the API console.
|
224
|
+
#
|
225
|
+
# @return [String] The API key.
|
226
|
+
attr_accessor :key
|
227
|
+
|
228
|
+
##
|
229
|
+
# The IP address of the user this request is being performed on behalf of.
|
230
|
+
#
|
231
|
+
# @return [String] The user's IP address.
|
232
|
+
attr_accessor :user_ip
|
233
|
+
|
234
|
+
##
|
235
|
+
# The user agent used by the client.
|
236
|
+
#
|
237
|
+
# @return [String]
|
238
|
+
# The user agent string used in the User-Agent header.
|
239
|
+
attr_accessor :user_agent
|
240
|
+
|
241
|
+
##
|
242
|
+
# The API hostname used by the client.
|
243
|
+
#
|
244
|
+
# @return [String]
|
245
|
+
# The API hostname. Should almost always be 'www.googleapis.com'.
|
246
|
+
attr_accessor :host
|
247
|
+
|
248
|
+
##
|
249
|
+
# The port number used by the client.
|
250
|
+
#
|
251
|
+
# @return [String]
|
252
|
+
# The port number. Should almost always be 443.
|
253
|
+
attr_accessor :port
|
254
|
+
|
255
|
+
##
|
256
|
+
# The base path used by the client for discovery.
|
257
|
+
#
|
258
|
+
# @return [String]
|
259
|
+
# The base path. Should almost always be '/discovery/v1'.
|
260
|
+
attr_accessor :discovery_path
|
261
|
+
|
262
|
+
##
|
263
|
+
# Number of times to retry on recoverable errors
|
264
|
+
#
|
265
|
+
# @return [FixNum]
|
266
|
+
# Number of retries
|
267
|
+
attr_accessor :retries
|
268
|
+
|
269
|
+
##
|
270
|
+
# Whether or not an expired auth token should be re-acquired
|
271
|
+
# (and the operation retried) regardless of retries setting
|
272
|
+
# @return [Boolean]
|
273
|
+
# Auto retry on auth expiry
|
274
|
+
attr_accessor :expired_auth_retry
|
275
|
+
|
276
|
+
##
|
277
|
+
# Returns the URI for the directory document.
|
278
|
+
#
|
279
|
+
# @return [Addressable::URI] The URI of the directory document.
|
280
|
+
def directory_uri
|
281
|
+
return resolve_uri(self.discovery_path + '/apis')
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# Manually registers a URI as a discovery document for a specific version
|
286
|
+
# of an API.
|
287
|
+
#
|
288
|
+
# @param [String, Symbol] api The API name.
|
289
|
+
# @param [String] version The desired version of the API.
|
290
|
+
# @param [Addressable::URI] uri The URI of the discovery document.
|
291
|
+
# @return [Google::APIClient::API] The service object.
|
292
|
+
def register_discovery_uri(api, version, uri)
|
293
|
+
api = api.to_s
|
294
|
+
version = version || 'v1'
|
295
|
+
@discovery_uris["#{api}:#{version}"] = uri
|
296
|
+
discovered_api(api, version)
|
297
|
+
end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Returns the URI for the discovery document.
|
301
|
+
#
|
302
|
+
# @param [String, Symbol] api The API name.
|
303
|
+
# @param [String] version The desired version of the API.
|
304
|
+
# @return [Addressable::URI] The URI of the discovery document.
|
305
|
+
def discovery_uri(api, version=nil)
|
306
|
+
api = api.to_s
|
307
|
+
version = version || 'v1'
|
308
|
+
return @discovery_uris["#{api}:#{version}"] ||= (
|
309
|
+
resolve_uri(
|
310
|
+
self.discovery_path + '/apis/{api}/{version}/rest',
|
311
|
+
'api' => api,
|
312
|
+
'version' => version
|
313
|
+
)
|
314
|
+
)
|
315
|
+
end
|
316
|
+
|
317
|
+
##
|
318
|
+
# Manually registers a pre-loaded discovery document for a specific version
|
319
|
+
# of an API.
|
320
|
+
#
|
321
|
+
# @param [String, Symbol] api The API name.
|
322
|
+
# @param [String] version The desired version of the API.
|
323
|
+
# @param [String, StringIO] discovery_document
|
324
|
+
# The contents of the discovery document.
|
325
|
+
# @return [Google::APIClient::API] The service object.
|
326
|
+
def register_discovery_document(api, version, discovery_document)
|
327
|
+
api = api.to_s
|
328
|
+
version = version || 'v1'
|
329
|
+
if discovery_document.kind_of?(StringIO)
|
330
|
+
discovery_document.rewind
|
331
|
+
discovery_document = discovery_document.string
|
332
|
+
elsif discovery_document.respond_to?(:to_str)
|
333
|
+
discovery_document = discovery_document.to_str
|
334
|
+
else
|
335
|
+
raise TypeError,
|
336
|
+
"Expected String or StringIO, got #{discovery_document.class}."
|
337
|
+
end
|
338
|
+
@discovery_documents["#{api}:#{version}"] =
|
339
|
+
MultiJson.load(discovery_document)
|
340
|
+
discovered_api(api, version)
|
341
|
+
end
|
342
|
+
|
343
|
+
##
|
344
|
+
# Returns the parsed directory document.
|
345
|
+
#
|
346
|
+
# @return [Hash] The parsed JSON from the directory document.
|
347
|
+
def directory_document
|
348
|
+
return @directory_document ||= (begin
|
349
|
+
response = self.execute!(
|
350
|
+
:http_method => :get,
|
351
|
+
:uri => self.directory_uri,
|
352
|
+
:authenticated => false
|
353
|
+
)
|
354
|
+
response.data
|
355
|
+
end)
|
356
|
+
end
|
357
|
+
|
358
|
+
##
|
359
|
+
# Returns the parsed discovery document.
|
360
|
+
#
|
361
|
+
# @param [String, Symbol] api The API name.
|
362
|
+
# @param [String] version The desired version of the API.
|
363
|
+
# @return [Hash] The parsed JSON from the discovery document.
|
364
|
+
def discovery_document(api, version=nil)
|
365
|
+
api = api.to_s
|
366
|
+
version = version || 'v1'
|
367
|
+
return @discovery_documents["#{api}:#{version}"] ||= (begin
|
368
|
+
response = self.execute!(
|
369
|
+
:http_method => :get,
|
370
|
+
:uri => self.discovery_uri(api, version),
|
371
|
+
:authenticated => false
|
372
|
+
)
|
373
|
+
response.data
|
374
|
+
end)
|
375
|
+
end
|
376
|
+
|
377
|
+
##
|
378
|
+
# Returns all APIs published in the directory document.
|
379
|
+
#
|
380
|
+
# @return [Array] The list of available APIs.
|
381
|
+
def discovered_apis
|
382
|
+
@directory_apis ||= (begin
|
383
|
+
document_base = self.directory_uri
|
384
|
+
if self.directory_document && self.directory_document['items']
|
385
|
+
self.directory_document['items'].map do |discovery_document|
|
386
|
+
Google::APIClient::API.new(
|
387
|
+
document_base,
|
388
|
+
discovery_document
|
389
|
+
)
|
390
|
+
end
|
391
|
+
else
|
392
|
+
[]
|
393
|
+
end
|
394
|
+
end)
|
395
|
+
end
|
396
|
+
|
397
|
+
##
|
398
|
+
# Returns the service object for a given service name and service version.
|
399
|
+
#
|
400
|
+
# @param [String, Symbol] api The API name.
|
401
|
+
# @param [String] version The desired version of the API.
|
402
|
+
#
|
403
|
+
# @return [Google::APIClient::API] The service object.
|
404
|
+
def discovered_api(api, version=nil)
|
405
|
+
if !api.kind_of?(String) && !api.kind_of?(Symbol)
|
406
|
+
raise TypeError,
|
407
|
+
"Expected String or Symbol, got #{api.class}."
|
408
|
+
end
|
409
|
+
api = api.to_s
|
410
|
+
version = version || 'v1'
|
411
|
+
return @discovered_apis["#{api}:#{version}"] ||= begin
|
412
|
+
document_base = self.discovery_uri(api, version)
|
413
|
+
discovery_document = self.discovery_document(api, version)
|
414
|
+
if document_base && discovery_document
|
415
|
+
Google::APIClient::API.new(
|
416
|
+
document_base,
|
417
|
+
discovery_document
|
418
|
+
)
|
419
|
+
else
|
420
|
+
nil
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
##
|
426
|
+
# Returns the method object for a given RPC name and service version.
|
427
|
+
#
|
428
|
+
# @param [String, Symbol] rpc_name The RPC name of the desired method.
|
429
|
+
# @param [String, Symbol] api The API the method is within.
|
430
|
+
# @param [String] version The desired version of the API.
|
431
|
+
#
|
432
|
+
# @return [Google::APIClient::Method] The method object.
|
433
|
+
def discovered_method(rpc_name, api, version=nil)
|
434
|
+
if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
|
435
|
+
raise TypeError,
|
436
|
+
"Expected String or Symbol, got #{rpc_name.class}."
|
437
|
+
end
|
438
|
+
rpc_name = rpc_name.to_s
|
439
|
+
api = api.to_s
|
440
|
+
version = version || 'v1'
|
441
|
+
service = self.discovered_api(api, version)
|
442
|
+
if service.to_h[rpc_name]
|
443
|
+
return service.to_h[rpc_name]
|
444
|
+
else
|
445
|
+
return nil
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
##
|
450
|
+
# Returns the service object with the highest version number.
|
451
|
+
#
|
452
|
+
# @note <em>Warning</em>: This method should be used with great care.
|
453
|
+
# As APIs are updated, minor differences between versions may cause
|
454
|
+
# incompatibilities. Requesting a specific version will avoid this issue.
|
455
|
+
#
|
456
|
+
# @param [String, Symbol] api The name of the service.
|
457
|
+
#
|
458
|
+
# @return [Google::APIClient::API] The service object.
|
459
|
+
def preferred_version(api)
|
460
|
+
if !api.kind_of?(String) && !api.kind_of?(Symbol)
|
461
|
+
raise TypeError,
|
462
|
+
"Expected String or Symbol, got #{api.class}."
|
463
|
+
end
|
464
|
+
api = api.to_s
|
465
|
+
return self.discovered_apis.detect do |a|
|
466
|
+
a.name == api && a.preferred == true
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
##
|
471
|
+
# Verifies an ID token against a server certificate. Used to ensure that
|
472
|
+
# an ID token supplied by an untrusted client-side mechanism is valid.
|
473
|
+
# Raises an error if the token is invalid or missing.
|
474
|
+
#
|
475
|
+
# @deprecated Use the google-id-token gem for verifying JWTs
|
476
|
+
def verify_id_token!
|
477
|
+
require 'jwt'
|
478
|
+
require 'openssl'
|
479
|
+
@certificates ||= {}
|
480
|
+
if !self.authorization.respond_to?(:id_token)
|
481
|
+
raise ArgumentError, (
|
482
|
+
"Current authorization mechanism does not support ID tokens: " +
|
483
|
+
"#{self.authorization.class.to_s}"
|
484
|
+
)
|
485
|
+
elsif !self.authorization.id_token
|
486
|
+
raise ArgumentError, (
|
487
|
+
"Could not verify ID token, ID token missing. " +
|
488
|
+
"Scopes were: #{self.authorization.scope.inspect}"
|
489
|
+
)
|
490
|
+
else
|
491
|
+
check_cached_certs = lambda do
|
492
|
+
valid = false
|
493
|
+
for _key, cert in @certificates
|
494
|
+
begin
|
495
|
+
self.authorization.decoded_id_token(cert.public_key)
|
496
|
+
valid = true
|
497
|
+
rescue JWT::DecodeError, Signet::UnsafeOperationError
|
498
|
+
# Expected exception. Ignore, ID token has not been validated.
|
499
|
+
end
|
500
|
+
end
|
501
|
+
valid
|
502
|
+
end
|
503
|
+
if check_cached_certs.call()
|
504
|
+
return true
|
505
|
+
end
|
506
|
+
response = self.execute!(
|
507
|
+
:http_method => :get,
|
508
|
+
:uri => 'https://www.googleapis.com/oauth2/v1/certs',
|
509
|
+
:authenticated => false
|
510
|
+
)
|
511
|
+
@certificates.merge!(
|
512
|
+
Hash[MultiJson.load(response.body).map do |key, cert|
|
513
|
+
[key, OpenSSL::X509::Certificate.new(cert)]
|
514
|
+
end]
|
515
|
+
)
|
516
|
+
if check_cached_certs.call()
|
517
|
+
return true
|
518
|
+
else
|
519
|
+
raise InvalidIDTokenError,
|
520
|
+
"Could not verify ID token against any available certificate."
|
521
|
+
end
|
522
|
+
end
|
523
|
+
return nil
|
524
|
+
end
|
525
|
+
|
526
|
+
##
|
527
|
+
# Generates a request.
|
528
|
+
#
|
529
|
+
# @option options [Google::APIClient::Method] :api_method
|
530
|
+
# The method object or the RPC name of the method being executed.
|
531
|
+
# @option options [Hash, Array] :parameters
|
532
|
+
# The parameters to send to the method.
|
533
|
+
# @option options [Hash, Array] :headers The HTTP headers for the request.
|
534
|
+
# @option options [String] :body The body of the request.
|
535
|
+
# @option options [String] :version ("v1")
|
536
|
+
# The service version. Only used if `api_method` is a `String`.
|
537
|
+
# @option options [#generate_authenticated_request] :authorization
|
538
|
+
# The authorization mechanism for the response. Used only if
|
539
|
+
# `:authenticated` is `true`.
|
540
|
+
# @option options [TrueClass, FalseClass] :authenticated (true)
|
541
|
+
# `true` if the request must be signed or somehow
|
542
|
+
# authenticated, `false` otherwise.
|
543
|
+
#
|
544
|
+
# @return [Google::APIClient::Reference] The generated request.
|
545
|
+
#
|
546
|
+
# @example
|
547
|
+
# request = client.generate_request(
|
548
|
+
# :api_method => 'plus.activities.list',
|
549
|
+
# :parameters =>
|
550
|
+
# {'collection' => 'public', 'userId' => 'me'}
|
551
|
+
# )
|
552
|
+
def generate_request(options={})
|
553
|
+
options = {
|
554
|
+
:api_client => self
|
555
|
+
}.merge(options)
|
556
|
+
return Google::APIClient::Request.new(options)
|
557
|
+
end
|
558
|
+
|
559
|
+
##
|
560
|
+
# Executes a request, wrapping it in a Result object.
|
561
|
+
#
|
562
|
+
# @param [Google::APIClient::Request, Hash, Array] params
|
563
|
+
# Either a Google::APIClient::Request, a Hash, or an Array.
|
564
|
+
#
|
565
|
+
# If a Google::APIClient::Request, no other parameters are expected.
|
566
|
+
#
|
567
|
+
# If a Hash, the below parameters are handled. If an Array, the
|
568
|
+
# parameters are assumed to be in the below order:
|
569
|
+
#
|
570
|
+
# - (Google::APIClient::Method) api_method:
|
571
|
+
# The method object or the RPC name of the method being executed.
|
572
|
+
# - (Hash, Array) parameters:
|
573
|
+
# The parameters to send to the method.
|
574
|
+
# - (String) body: The body of the request.
|
575
|
+
# - (Hash, Array) headers: The HTTP headers for the request.
|
576
|
+
# - (Hash) options: A set of options for the request, of which:
|
577
|
+
# - (#generate_authenticated_request) :authorization (default: true) -
|
578
|
+
# The authorization mechanism for the response. Used only if
|
579
|
+
# `:authenticated` is `true`.
|
580
|
+
# - (TrueClass, FalseClass) :authenticated (default: true) -
|
581
|
+
# `true` if the request must be signed or somehow
|
582
|
+
# authenticated, `false` otherwise.
|
583
|
+
# - (TrueClass, FalseClass) :gzip (default: true) -
|
584
|
+
# `true` if gzip enabled, `false` otherwise.
|
585
|
+
# - (FixNum) :retries -
|
586
|
+
# # of times to retry on recoverable errors
|
587
|
+
#
|
588
|
+
# @return [Google::APIClient::Result] The result from the API, nil if batch.
|
589
|
+
#
|
590
|
+
# @example
|
591
|
+
# result = client.execute(batch_request)
|
592
|
+
#
|
593
|
+
# @example
|
594
|
+
# plus = client.discovered_api('plus')
|
595
|
+
# result = client.execute(
|
596
|
+
# :api_method => plus.activities.list,
|
597
|
+
# :parameters => {'collection' => 'public', 'userId' => 'me'}
|
598
|
+
# )
|
599
|
+
#
|
600
|
+
# @see Google::APIClient#generate_request
|
601
|
+
def execute!(*params)
|
602
|
+
if params.first.kind_of?(Google::APIClient::Request)
|
603
|
+
request = params.shift
|
604
|
+
options = params.shift || {}
|
605
|
+
else
|
606
|
+
# This block of code allows us to accept multiple parameter passing
|
607
|
+
# styles, and maintaining some backwards compatibility.
|
608
|
+
#
|
609
|
+
# Note: I'm extremely tempted to deprecate this style of execute call.
|
610
|
+
if params.last.respond_to?(:to_hash) && params.size == 1
|
611
|
+
options = params.pop
|
612
|
+
else
|
613
|
+
options = {}
|
614
|
+
end
|
615
|
+
|
616
|
+
options[:api_method] = params.shift if params.size > 0
|
617
|
+
options[:parameters] = params.shift if params.size > 0
|
618
|
+
options[:body] = params.shift if params.size > 0
|
619
|
+
options[:headers] = params.shift if params.size > 0
|
620
|
+
options.update(params.shift) if params.size > 0
|
621
|
+
request = self.generate_request(options)
|
622
|
+
end
|
623
|
+
|
624
|
+
request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil?
|
625
|
+
request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false
|
626
|
+
request.headers['Content-Type'] ||= ''
|
627
|
+
request.parameters['key'] ||= self.key unless self.key.nil?
|
628
|
+
request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?
|
629
|
+
|
630
|
+
connection = options[:connection] || self.connection
|
631
|
+
request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
|
632
|
+
|
633
|
+
tries = 1 + (options[:retries] || self.retries)
|
634
|
+
attempt = 0
|
635
|
+
|
636
|
+
Retriable.retriable :tries => tries,
|
637
|
+
:on => [TransmissionError],
|
638
|
+
:on_retry => client_error_handler,
|
639
|
+
:interval => lambda {|attempts| (2 ** attempts) + rand} do
|
640
|
+
attempt += 1
|
641
|
+
|
642
|
+
# This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
|
643
|
+
# auth to be re-attempted without having to retry all sorts of other failures like
|
644
|
+
# NotFound, etc
|
645
|
+
Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1,
|
646
|
+
:on => [AuthorizationError],
|
647
|
+
:on_retry => authorization_error_handler(request.authorization) do
|
648
|
+
result = request.send(connection, true)
|
649
|
+
|
650
|
+
case result.status
|
651
|
+
when 200...300
|
652
|
+
result
|
653
|
+
when 301, 302, 303, 307
|
654
|
+
request = generate_request(request.to_hash.merge({
|
655
|
+
:uri => result.headers['location'],
|
656
|
+
:api_method => nil
|
657
|
+
}))
|
658
|
+
raise RedirectError.new(result.headers['location'], result)
|
659
|
+
when 401
|
660
|
+
raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result)
|
661
|
+
when 400, 402...500
|
662
|
+
raise ClientError.new(result.error_message || "A client error has occurred", result)
|
663
|
+
when 500...600
|
664
|
+
raise ServerError.new(result.error_message || "A server error has occurred", result)
|
665
|
+
else
|
666
|
+
raise TransmissionError.new(result.error_message || "A transmission error has occurred", result)
|
667
|
+
end
|
668
|
+
end
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
##
|
673
|
+
# Same as Google::APIClient#execute!, but does not raise an exception for
|
674
|
+
# normal API errros.
|
675
|
+
#
|
676
|
+
# @see Google::APIClient#execute
|
677
|
+
def execute(*params)
|
678
|
+
begin
|
679
|
+
return self.execute!(*params)
|
680
|
+
rescue TransmissionError => e
|
681
|
+
return e.result
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
protected
|
686
|
+
|
687
|
+
##
|
688
|
+
# Resolves a URI template against the client's configured base.
|
689
|
+
#
|
690
|
+
# @api private
|
691
|
+
# @param [String, Addressable::URI, Addressable::Template] template
|
692
|
+
# The template to resolve.
|
693
|
+
# @param [Hash] mapping The mapping that corresponds to the template.
|
694
|
+
# @return [Addressable::URI] The expanded URI.
|
695
|
+
def resolve_uri(template, mapping={})
|
696
|
+
@base_uri ||= Addressable::URI.new(
|
697
|
+
:scheme => 'https',
|
698
|
+
:host => self.host,
|
699
|
+
:port => self.port
|
700
|
+
).normalize
|
701
|
+
template = if template.kind_of?(Addressable::Template)
|
702
|
+
template.pattern
|
703
|
+
elsif template.respond_to?(:to_str)
|
704
|
+
template.to_str
|
705
|
+
else
|
706
|
+
raise TypeError,
|
707
|
+
"Expected String, Addressable::URI, or Addressable::Template, " +
|
708
|
+
"got #{template.class}."
|
709
|
+
end
|
710
|
+
return Addressable::Template.new(@base_uri + template).expand(mapping)
|
711
|
+
end
|
712
|
+
|
713
|
+
|
714
|
+
##
|
715
|
+
# Returns on proc for special processing of retries for authorization errors
|
716
|
+
# Only 401s should be retried and only if the credentials are refreshable
|
717
|
+
#
|
718
|
+
# @param [#fetch_access_token!] authorization
|
719
|
+
# OAuth 2 credentials
|
720
|
+
# @return [Proc]
|
721
|
+
def authorization_error_handler(authorization)
|
722
|
+
can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token
|
723
|
+
Proc.new do |exception, tries|
|
724
|
+
next unless exception.kind_of?(AuthorizationError)
|
725
|
+
if can_refresh
|
726
|
+
begin
|
727
|
+
logger.debug("Attempting refresh of access token & retry of request")
|
728
|
+
authorization.fetch_access_token!
|
729
|
+
next
|
730
|
+
rescue Signet::AuthorizationError
|
731
|
+
end
|
732
|
+
end
|
733
|
+
raise exception
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
##
|
738
|
+
# Returns on proc for special processing of retries as not all client errors
|
739
|
+
# are recoverable. Only 401s should be retried (via authorization_error_handler)
|
740
|
+
#
|
741
|
+
# @return [Proc]
|
742
|
+
def client_error_handler
|
743
|
+
Proc.new do |exception, tries|
|
744
|
+
raise exception if exception.kind_of?(ClientError)
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
end
|
749
|
+
|
750
|
+
end
|