ghost_google-api-client 0.4.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.
- data/CHANGELOG.md +77 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +80 -0
- data/LICENSE +202 -0
- data/README.md +71 -0
- data/Rakefile +42 -0
- data/bin/google-api +545 -0
- data/lib/compat/multi_json.rb +17 -0
- data/lib/google/api_client.rb +802 -0
- data/lib/google/api_client/batch.rb +296 -0
- data/lib/google/api_client/client_secrets.rb +106 -0
- data/lib/google/api_client/discovery.rb +19 -0
- data/lib/google/api_client/discovery/api.rb +287 -0
- data/lib/google/api_client/discovery/media.rb +77 -0
- data/lib/google/api_client/discovery/method.rb +369 -0
- data/lib/google/api_client/discovery/resource.rb +150 -0
- data/lib/google/api_client/discovery/schema.rb +119 -0
- data/lib/google/api_client/environment.rb +42 -0
- data/lib/google/api_client/errors.rb +49 -0
- data/lib/google/api_client/media.rb +172 -0
- data/lib/google/api_client/reference.rb +305 -0
- data/lib/google/api_client/result.rb +161 -0
- data/lib/google/api_client/service_account.rb +134 -0
- data/lib/google/api_client/version.rb +31 -0
- data/lib/google/inflection.rb +28 -0
- data/spec/fixtures/files/sample.txt +33 -0
- data/spec/google/api_client/batch_spec.rb +241 -0
- data/spec/google/api_client/discovery_spec.rb +670 -0
- data/spec/google/api_client/media_spec.rb +143 -0
- data/spec/google/api_client/result_spec.rb +185 -0
- data/spec/google/api_client/service_account_spec.rb +58 -0
- data/spec/google/api_client_spec.rb +139 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/gem.rake +97 -0
- data/tasks/git.rake +45 -0
- data/tasks/metrics.rake +22 -0
- data/tasks/spec.rake +57 -0
- data/tasks/wiki.rake +82 -0
- data/tasks/yard.rake +29 -0
- metadata +253 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
gem 'multi_json', '>= 1.0.0'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
unless MultiJson.respond_to?(:load)
|
5
|
+
module MultiJson
|
6
|
+
class <<self
|
7
|
+
alias :load :decode
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
unless MultiJson.respond_to?(:dump)
|
12
|
+
module MultiJson
|
13
|
+
class <<self
|
14
|
+
alias :dump :encode
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,802 @@
|
|
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
|
+
gem 'faraday', '~> 0.8.1'
|
17
|
+
require 'faraday'
|
18
|
+
require 'faraday/utils'
|
19
|
+
require 'multi_json'
|
20
|
+
require 'compat/multi_json'
|
21
|
+
require 'stringio'
|
22
|
+
|
23
|
+
require 'google/api_client/version'
|
24
|
+
require 'google/api_client/errors'
|
25
|
+
require 'google/api_client/environment'
|
26
|
+
require 'google/api_client/discovery'
|
27
|
+
require 'google/api_client/reference'
|
28
|
+
require 'google/api_client/result'
|
29
|
+
require 'google/api_client/media'
|
30
|
+
require 'google/api_client/service_account'
|
31
|
+
require 'google/api_client/batch'
|
32
|
+
|
33
|
+
module Google
|
34
|
+
# TODO(bobaman): Document all this stuff.
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
# This class manages APIs communication.
|
39
|
+
class APIClient
|
40
|
+
##
|
41
|
+
# Creates a new Google API client.
|
42
|
+
#
|
43
|
+
# @param [Hash] options The configuration parameters for the client.
|
44
|
+
# @option options [Symbol, #generate_authenticated_request] :authorization
|
45
|
+
# (:oauth_1)
|
46
|
+
# The authorization mechanism used by the client. The following
|
47
|
+
# mechanisms are supported out-of-the-box:
|
48
|
+
# <ul>
|
49
|
+
# <li><code>:two_legged_oauth_1</code></li>
|
50
|
+
# <li><code>:oauth_1</code></li>
|
51
|
+
# <li><code>:oauth_2</code></li>
|
52
|
+
# </ul>
|
53
|
+
# @option options [String] :application_name
|
54
|
+
# The name of the application using the client.
|
55
|
+
# @option options [String] :application_version
|
56
|
+
# The version number of the application using the client.
|
57
|
+
# @option options [String] :user_agent
|
58
|
+
# ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}")
|
59
|
+
# The user agent used by the client. Most developers will want to
|
60
|
+
# leave this value alone and use the `:application_name` option instead.
|
61
|
+
# @option options [String] :host ("www.googleapis.com")
|
62
|
+
# The API hostname used by the client. This rarely needs to be changed.
|
63
|
+
# @option options [String] :port (443)
|
64
|
+
# The port number used by the client. This rarely needs to be changed.
|
65
|
+
# @option options [String] :discovery_path ("/discovery/v1")
|
66
|
+
# The discovery base path. This rarely needs to be changed.
|
67
|
+
def initialize(options={})
|
68
|
+
# Normalize key to String to allow indifferent access.
|
69
|
+
options = options.inject({}) do |accu, (key, value)|
|
70
|
+
accu[key.to_s] = value
|
71
|
+
accu
|
72
|
+
end
|
73
|
+
# Almost all API usage will have a host of 'www.googleapis.com'.
|
74
|
+
self.host = options["host"] || 'www.googleapis.com'
|
75
|
+
self.port = options["port"] || 443
|
76
|
+
self.discovery_path = options["discovery_path"] || '/discovery/v1'
|
77
|
+
|
78
|
+
# Most developers will want to leave this value alone and use the
|
79
|
+
# application_name option.
|
80
|
+
application_string = (
|
81
|
+
options["application_name"] ? (
|
82
|
+
"#{options["application_name"]}/" +
|
83
|
+
"#{options["application_version"] || '0.0.0'}"
|
84
|
+
) : ""
|
85
|
+
)
|
86
|
+
self.user_agent = options["user_agent"] || (
|
87
|
+
"#{application_string} " +
|
88
|
+
"google-api-ruby-client/#{VERSION::STRING} " +
|
89
|
+
ENV::OS_VERSION
|
90
|
+
).strip
|
91
|
+
# The writer method understands a few Symbols and will generate useful
|
92
|
+
# default authentication mechanisms.
|
93
|
+
self.authorization =
|
94
|
+
options.key?("authorization") ? options["authorization"] : :oauth_2
|
95
|
+
self.key = options["key"]
|
96
|
+
self.user_ip = options["user_ip"]
|
97
|
+
@discovery_uris = {}
|
98
|
+
@discovery_documents = {}
|
99
|
+
@discovered_apis = {}
|
100
|
+
return self
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Returns the authorization mechanism used by the client.
|
105
|
+
#
|
106
|
+
# @return [#generate_authenticated_request] The authorization mechanism.
|
107
|
+
attr_reader :authorization
|
108
|
+
|
109
|
+
##
|
110
|
+
# Sets the authorization mechanism used by the client.
|
111
|
+
#
|
112
|
+
# @param [#generate_authenticated_request] new_authorization
|
113
|
+
# The new authorization mechanism.
|
114
|
+
def authorization=(new_authorization)
|
115
|
+
case new_authorization
|
116
|
+
when :oauth_1, :oauth
|
117
|
+
gem 'signet', '~> 0.4.0'
|
118
|
+
require 'signet/oauth_1/client'
|
119
|
+
# NOTE: Do not rely on this default value, as it may change
|
120
|
+
new_authorization = Signet::OAuth1::Client.new(
|
121
|
+
:temporary_credential_uri =>
|
122
|
+
'https://www.google.com/accounts/OAuthGetRequestToken',
|
123
|
+
:authorization_uri =>
|
124
|
+
'https://www.google.com/accounts/OAuthAuthorizeToken',
|
125
|
+
:token_credential_uri =>
|
126
|
+
'https://www.google.com/accounts/OAuthGetAccessToken',
|
127
|
+
:client_credential_key => 'anonymous',
|
128
|
+
:client_credential_secret => 'anonymous'
|
129
|
+
)
|
130
|
+
when :two_legged_oauth_1, :two_legged_oauth
|
131
|
+
gem 'signet', '~> 0.4.0'
|
132
|
+
require 'signet/oauth_1/client'
|
133
|
+
# NOTE: Do not rely on this default value, as it may change
|
134
|
+
new_authorization = Signet::OAuth1::Client.new(
|
135
|
+
:client_credential_key => nil,
|
136
|
+
:client_credential_secret => nil,
|
137
|
+
:two_legged => true
|
138
|
+
)
|
139
|
+
when :oauth_2
|
140
|
+
gem 'signet', '~> 0.4.0'
|
141
|
+
require 'signet/oauth_2/client'
|
142
|
+
# NOTE: Do not rely on this default value, as it may change
|
143
|
+
new_authorization = Signet::OAuth2::Client.new(
|
144
|
+
:authorization_uri =>
|
145
|
+
'https://accounts.google.com/o/oauth2/auth',
|
146
|
+
:token_credential_uri =>
|
147
|
+
'https://accounts.google.com/o/oauth2/token'
|
148
|
+
)
|
149
|
+
when nil
|
150
|
+
# No authorization mechanism
|
151
|
+
else
|
152
|
+
if !new_authorization.respond_to?(:generate_authenticated_request)
|
153
|
+
raise TypeError,
|
154
|
+
'Expected authorization mechanism to respond to ' +
|
155
|
+
'#generate_authenticated_request.'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
@authorization = new_authorization
|
159
|
+
return @authorization
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# The application's API key issued by the API console.
|
164
|
+
#
|
165
|
+
# @return [String] The API key.
|
166
|
+
attr_accessor :key
|
167
|
+
|
168
|
+
##
|
169
|
+
# The IP address of the user this request is being performed on behalf of.
|
170
|
+
#
|
171
|
+
# @return [String] The user's IP address.
|
172
|
+
attr_accessor :user_ip
|
173
|
+
|
174
|
+
##
|
175
|
+
# The user agent used by the client.
|
176
|
+
#
|
177
|
+
# @return [String]
|
178
|
+
# The user agent string used in the User-Agent header.
|
179
|
+
attr_accessor :user_agent
|
180
|
+
|
181
|
+
##
|
182
|
+
# The API hostname used by the client.
|
183
|
+
#
|
184
|
+
# @return [String]
|
185
|
+
# The API hostname. Should almost always be 'www.googleapis.com'.
|
186
|
+
attr_accessor :host
|
187
|
+
|
188
|
+
##
|
189
|
+
# The port number used by the client.
|
190
|
+
#
|
191
|
+
# @return [String]
|
192
|
+
# The port number. Should almost always be 443.
|
193
|
+
attr_accessor :port
|
194
|
+
|
195
|
+
##
|
196
|
+
# The base path used by the client for discovery.
|
197
|
+
#
|
198
|
+
# @return [String]
|
199
|
+
# The base path. Should almost always be '/discovery/v1'.
|
200
|
+
attr_accessor :discovery_path
|
201
|
+
|
202
|
+
##
|
203
|
+
# Resolves a URI template against the client's configured base.
|
204
|
+
#
|
205
|
+
# @param [String, Addressable::URI, Addressable::Template] template
|
206
|
+
# The template to resolve.
|
207
|
+
# @param [Hash] mapping The mapping that corresponds to the template.
|
208
|
+
# @return [Addressable::URI] The expanded URI.
|
209
|
+
def resolve_uri(template, mapping={})
|
210
|
+
@base_uri ||= Addressable::URI.new(
|
211
|
+
:scheme => 'https',
|
212
|
+
:host => self.host,
|
213
|
+
:port => self.port
|
214
|
+
).normalize
|
215
|
+
template = if template.kind_of?(Addressable::Template)
|
216
|
+
template.pattern
|
217
|
+
elsif template.respond_to?(:to_str)
|
218
|
+
template.to_str
|
219
|
+
else
|
220
|
+
raise TypeError,
|
221
|
+
"Expected String, Addressable::URI, or Addressable::Template, " +
|
222
|
+
"got #{template.class}."
|
223
|
+
end
|
224
|
+
return Addressable::Template.new(@base_uri + template).expand(mapping)
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Returns the URI for the directory document.
|
229
|
+
#
|
230
|
+
# @return [Addressable::URI] The URI of the directory document.
|
231
|
+
def directory_uri
|
232
|
+
return resolve_uri(self.discovery_path + '/apis')
|
233
|
+
end
|
234
|
+
|
235
|
+
##
|
236
|
+
# Manually registers a URI as a discovery document for a specific version
|
237
|
+
# of an API.
|
238
|
+
#
|
239
|
+
# @param [String, Symbol] api The API name.
|
240
|
+
# @param [String] version The desired version of the API.
|
241
|
+
# @param [Addressable::URI] uri The URI of the discovery document.
|
242
|
+
def register_discovery_uri(api, version, uri)
|
243
|
+
api = api.to_s
|
244
|
+
version = version || 'v1'
|
245
|
+
@discovery_uris["#{api}:#{version}"] = uri
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Returns the URI for the discovery document.
|
250
|
+
#
|
251
|
+
# @param [String, Symbol] api The API name.
|
252
|
+
# @param [String] version The desired version of the API.
|
253
|
+
# @return [Addressable::URI] The URI of the discovery document.
|
254
|
+
def discovery_uri(api, version=nil)
|
255
|
+
api = api.to_s
|
256
|
+
version = version || 'v1'
|
257
|
+
return @discovery_uris["#{api}:#{version}"] ||= (
|
258
|
+
resolve_uri(
|
259
|
+
self.discovery_path + '/apis/{api}/{version}/rest',
|
260
|
+
'api' => api,
|
261
|
+
'version' => version
|
262
|
+
)
|
263
|
+
)
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Manually registers a pre-loaded discovery document for a specific version
|
268
|
+
# of an API.
|
269
|
+
#
|
270
|
+
# @param [String, Symbol] api The API name.
|
271
|
+
# @param [String] version The desired version of the API.
|
272
|
+
# @param [String, StringIO] discovery_document
|
273
|
+
# The contents of the discovery document.
|
274
|
+
def register_discovery_document(api, version, discovery_document)
|
275
|
+
api = api.to_s
|
276
|
+
version = version || 'v1'
|
277
|
+
if discovery_document.kind_of?(StringIO)
|
278
|
+
discovery_document.rewind
|
279
|
+
discovery_document = discovery_document.string
|
280
|
+
elsif discovery_document.respond_to?(:to_str)
|
281
|
+
discovery_document = discovery_document.to_str
|
282
|
+
else
|
283
|
+
raise TypeError,
|
284
|
+
"Expected String or StringIO, got #{discovery_document.class}."
|
285
|
+
end
|
286
|
+
@discovery_documents["#{api}:#{version}"] =
|
287
|
+
MultiJson.load(discovery_document)
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# Returns the parsed directory document.
|
292
|
+
#
|
293
|
+
# @return [Hash] The parsed JSON from the directory document.
|
294
|
+
def directory_document
|
295
|
+
return @directory_document ||= (begin
|
296
|
+
request = self.generate_request(
|
297
|
+
:http_method => :get,
|
298
|
+
:uri => self.directory_uri,
|
299
|
+
:authenticated => false
|
300
|
+
)
|
301
|
+
response = self.transmit(:request => request)
|
302
|
+
if response.status >= 200 && response.status < 300
|
303
|
+
MultiJson.load(response.body)
|
304
|
+
elsif response.status >= 400
|
305
|
+
case response.status
|
306
|
+
when 400...500
|
307
|
+
exception_type = ClientError
|
308
|
+
when 500...600
|
309
|
+
exception_type = ServerError
|
310
|
+
else
|
311
|
+
exception_type = TransmissionError
|
312
|
+
end
|
313
|
+
url = request.to_env(Faraday.default_connection)[:url]
|
314
|
+
raise exception_type,
|
315
|
+
"Could not retrieve directory document at: #{url}"
|
316
|
+
end
|
317
|
+
end)
|
318
|
+
end
|
319
|
+
|
320
|
+
##
|
321
|
+
# Returns the parsed discovery document.
|
322
|
+
#
|
323
|
+
# @param [String, Symbol] api The API name.
|
324
|
+
# @param [String] version The desired version of the API.
|
325
|
+
# @return [Hash] The parsed JSON from the discovery document.
|
326
|
+
def discovery_document(api, version=nil)
|
327
|
+
api = api.to_s
|
328
|
+
version = version || 'v1'
|
329
|
+
return @discovery_documents["#{api}:#{version}"] ||= (begin
|
330
|
+
request = self.generate_request(
|
331
|
+
:http_method => :get,
|
332
|
+
:uri => self.discovery_uri(api, version),
|
333
|
+
:authenticated => false
|
334
|
+
)
|
335
|
+
response = self.transmit(:request => request)
|
336
|
+
if response.status >= 200 && response.status < 300
|
337
|
+
MultiJson.load(response.body)
|
338
|
+
elsif response.status >= 400
|
339
|
+
case response.status
|
340
|
+
when 400...500
|
341
|
+
exception_type = ClientError
|
342
|
+
when 500...600
|
343
|
+
exception_type = ServerError
|
344
|
+
else
|
345
|
+
exception_type = TransmissionError
|
346
|
+
end
|
347
|
+
url = request.to_env(Faraday.default_connection)[:url]
|
348
|
+
raise exception_type,
|
349
|
+
"Could not retrieve discovery document at: #{url}"
|
350
|
+
end
|
351
|
+
end)
|
352
|
+
end
|
353
|
+
|
354
|
+
##
|
355
|
+
# Returns all APIs published in the directory document.
|
356
|
+
#
|
357
|
+
# @return [Array] The list of available APIs.
|
358
|
+
def discovered_apis
|
359
|
+
@directory_apis ||= (begin
|
360
|
+
document_base = self.directory_uri
|
361
|
+
if self.directory_document && self.directory_document['items']
|
362
|
+
self.directory_document['items'].map do |discovery_document|
|
363
|
+
Google::APIClient::API.new(
|
364
|
+
document_base,
|
365
|
+
discovery_document
|
366
|
+
)
|
367
|
+
end
|
368
|
+
else
|
369
|
+
[]
|
370
|
+
end
|
371
|
+
end)
|
372
|
+
end
|
373
|
+
|
374
|
+
##
|
375
|
+
# Returns the service object for a given service name and service version.
|
376
|
+
#
|
377
|
+
# @param [String, Symbol] api The API name.
|
378
|
+
# @param [String] version The desired version of the API.
|
379
|
+
#
|
380
|
+
# @return [Google::APIClient::API] The service object.
|
381
|
+
def discovered_api(api, version=nil)
|
382
|
+
if !api.kind_of?(String) && !api.kind_of?(Symbol)
|
383
|
+
raise TypeError,
|
384
|
+
"Expected String or Symbol, got #{api.class}."
|
385
|
+
end
|
386
|
+
api = api.to_s
|
387
|
+
version = version || 'v1'
|
388
|
+
return @discovered_apis["#{api}:#{version}"] ||= begin
|
389
|
+
document_base = self.discovery_uri(api, version)
|
390
|
+
discovery_document = self.discovery_document(api, version)
|
391
|
+
if document_base && discovery_document
|
392
|
+
Google::APIClient::API.new(
|
393
|
+
document_base,
|
394
|
+
discovery_document
|
395
|
+
)
|
396
|
+
else
|
397
|
+
nil
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
##
|
403
|
+
# Returns the method object for a given RPC name and service version.
|
404
|
+
#
|
405
|
+
# @param [String, Symbol] rpc_name The RPC name of the desired method.
|
406
|
+
# @param [String, Symbol] rpc_name The API the method is within.
|
407
|
+
# @param [String] version The desired version of the API.
|
408
|
+
#
|
409
|
+
# @return [Google::APIClient::Method] The method object.
|
410
|
+
def discovered_method(rpc_name, api, version=nil)
|
411
|
+
if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
|
412
|
+
raise TypeError,
|
413
|
+
"Expected String or Symbol, got #{rpc_name.class}."
|
414
|
+
end
|
415
|
+
rpc_name = rpc_name.to_s
|
416
|
+
api = api.to_s
|
417
|
+
version = version || 'v1'
|
418
|
+
service = self.discovered_api(api, version)
|
419
|
+
if service.to_h[rpc_name]
|
420
|
+
return service.to_h[rpc_name]
|
421
|
+
else
|
422
|
+
return nil
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
##
|
427
|
+
# Returns the service object with the highest version number.
|
428
|
+
#
|
429
|
+
# @note <em>Warning</em>: This method should be used with great care.
|
430
|
+
# As APIs are updated, minor differences between versions may cause
|
431
|
+
# incompatibilities. Requesting a specific version will avoid this issue.
|
432
|
+
#
|
433
|
+
# @param [String, Symbol] api The name of the service.
|
434
|
+
#
|
435
|
+
# @return [Google::APIClient::API] The service object.
|
436
|
+
def preferred_version(api)
|
437
|
+
if !api.kind_of?(String) && !api.kind_of?(Symbol)
|
438
|
+
raise TypeError,
|
439
|
+
"Expected String or Symbol, got #{api.class}."
|
440
|
+
end
|
441
|
+
api = api.to_s
|
442
|
+
return self.discovered_apis.detect do |a|
|
443
|
+
a.name == api && a.preferred == true
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
##
|
448
|
+
# Verifies an ID token against a server certificate. Used to ensure that
|
449
|
+
# an ID token supplied by an untrusted client-side mechanism is valid.
|
450
|
+
# Raises an error if the token is invalid or missing.
|
451
|
+
def verify_id_token!
|
452
|
+
gem 'jwt', '~> 0.1.4'
|
453
|
+
require 'jwt'
|
454
|
+
require 'openssl'
|
455
|
+
@certificates ||= {}
|
456
|
+
if !self.authorization.respond_to?(:id_token)
|
457
|
+
raise ArgumentError, (
|
458
|
+
"Current authorization mechanism does not support ID tokens: " +
|
459
|
+
"#{self.authorization.class.to_s}"
|
460
|
+
)
|
461
|
+
elsif !self.authorization.id_token
|
462
|
+
raise ArgumentError, (
|
463
|
+
"Could not verify ID token, ID token missing. " +
|
464
|
+
"Scopes were: #{self.authorization.scope.inspect}"
|
465
|
+
)
|
466
|
+
else
|
467
|
+
check_cached_certs = lambda do
|
468
|
+
valid = false
|
469
|
+
for key, cert in @certificates
|
470
|
+
begin
|
471
|
+
self.authorization.decoded_id_token(cert.public_key)
|
472
|
+
valid = true
|
473
|
+
rescue JWT::DecodeError, Signet::UnsafeOperationError
|
474
|
+
# Expected exception. Ignore, ID token has not been validated.
|
475
|
+
end
|
476
|
+
end
|
477
|
+
valid
|
478
|
+
end
|
479
|
+
if check_cached_certs.call()
|
480
|
+
return true
|
481
|
+
end
|
482
|
+
request = self.generate_request(
|
483
|
+
:http_method => :get,
|
484
|
+
:uri => 'https://www.googleapis.com/oauth2/v1/certs',
|
485
|
+
:authenticated => false
|
486
|
+
)
|
487
|
+
response = self.transmit(:request => request)
|
488
|
+
if response.status >= 200 && response.status < 300
|
489
|
+
@certificates.merge!(
|
490
|
+
Hash[MultiJson.load(response.body).map do |key, cert|
|
491
|
+
[key, OpenSSL::X509::Certificate.new(cert)]
|
492
|
+
end]
|
493
|
+
)
|
494
|
+
elsif response.status >= 400
|
495
|
+
case response.status
|
496
|
+
when 400...500
|
497
|
+
exception_type = ClientError
|
498
|
+
when 500...600
|
499
|
+
exception_type = ServerError
|
500
|
+
else
|
501
|
+
exception_type = TransmissionError
|
502
|
+
end
|
503
|
+
url = request.to_env(Faraday.default_connection)[:url]
|
504
|
+
raise exception_type,
|
505
|
+
"Could not retrieve certificates from: #{url}"
|
506
|
+
end
|
507
|
+
if check_cached_certs.call()
|
508
|
+
return true
|
509
|
+
else
|
510
|
+
raise InvalidIDTokenError,
|
511
|
+
"Could not verify ID token against any available certificate."
|
512
|
+
end
|
513
|
+
end
|
514
|
+
return nil
|
515
|
+
end
|
516
|
+
|
517
|
+
##
|
518
|
+
# Generates a request.
|
519
|
+
#
|
520
|
+
# @option options [Google::APIClient::Method, String] :api_method
|
521
|
+
# The method object or the RPC name of the method being executed.
|
522
|
+
# @option options [Hash, Array] :parameters
|
523
|
+
# The parameters to send to the method.
|
524
|
+
# @option options [Hash, Array] :headers The HTTP headers for the request.
|
525
|
+
# @option options [String] :body The body of the request.
|
526
|
+
# @option options [String] :version ("v1")
|
527
|
+
# The service version. Only used if `api_method` is a `String`.
|
528
|
+
# @option options [#generate_authenticated_request] :authorization
|
529
|
+
# The authorization mechanism for the response. Used only if
|
530
|
+
# `:authenticated` is `true`.
|
531
|
+
# @option options [TrueClass, FalseClass] :authenticated (true)
|
532
|
+
# `true` if the request must be signed or somehow
|
533
|
+
# authenticated, `false` otherwise.
|
534
|
+
#
|
535
|
+
# @return [Faraday::Request] The generated request.
|
536
|
+
#
|
537
|
+
# @example
|
538
|
+
# request = client.generate_request(
|
539
|
+
# :api_method => 'plus.activities.list',
|
540
|
+
# :parameters =>
|
541
|
+
# {'collection' => 'public', 'userId' => 'me'}
|
542
|
+
# )
|
543
|
+
def generate_request(options={})
|
544
|
+
# Note: The merge method on a Hash object will coerce an API Reference
|
545
|
+
# object into a Hash and merge with the default options.
|
546
|
+
|
547
|
+
options={
|
548
|
+
:version => 'v1',
|
549
|
+
:authorization => self.authorization,
|
550
|
+
:key => self.key,
|
551
|
+
:user_ip => self.user_ip,
|
552
|
+
:connection => Faraday.default_connection
|
553
|
+
}.merge(options)
|
554
|
+
|
555
|
+
# The Reference object is going to need this to do method ID lookups.
|
556
|
+
options[:client] = self
|
557
|
+
# The default value for the :authenticated option depends on whether an
|
558
|
+
# authorization mechanism has been set.
|
559
|
+
if options[:authorization]
|
560
|
+
options = {:authenticated => true}.merge(options)
|
561
|
+
else
|
562
|
+
options = {:authenticated => false}.merge(options)
|
563
|
+
end
|
564
|
+
reference = Google::APIClient::Reference.new(options)
|
565
|
+
request = reference.to_request
|
566
|
+
if options[:authenticated]
|
567
|
+
request = options[:authorization].generate_authenticated_request(
|
568
|
+
:request => request,
|
569
|
+
:connection => options[:connection]
|
570
|
+
)
|
571
|
+
end
|
572
|
+
return request
|
573
|
+
end
|
574
|
+
|
575
|
+
##
|
576
|
+
# Signs a request using the current authorization mechanism.
|
577
|
+
#
|
578
|
+
# @param [Hash] options a customizable set of options
|
579
|
+
#
|
580
|
+
# @return [Faraday::Request] The signed or otherwise authenticated request.
|
581
|
+
# @deprecated No longer used internally
|
582
|
+
def generate_authenticated_request(options={})
|
583
|
+
return authorization.generate_authenticated_request(options)
|
584
|
+
end
|
585
|
+
|
586
|
+
##
|
587
|
+
# Transmits the request using the current HTTP adapter.
|
588
|
+
#
|
589
|
+
# @option options [Array, Faraday::Request] :request
|
590
|
+
# The HTTP request to transmit.
|
591
|
+
# @option options [String, Symbol] :method
|
592
|
+
# The method for the HTTP request.
|
593
|
+
# @option options [String, Addressable::URI] :uri
|
594
|
+
# The URI for the HTTP request.
|
595
|
+
# @option options [Array, Hash] :headers
|
596
|
+
# The headers for the HTTP request.
|
597
|
+
# @option options [String] :body
|
598
|
+
# The body for the HTTP request.
|
599
|
+
# @option options [Faraday::Connection] :connection
|
600
|
+
# The HTTP connection to use.
|
601
|
+
#
|
602
|
+
# @return [Faraday::Response] The response from the server.
|
603
|
+
def transmit(options={})
|
604
|
+
options[:connection] ||= Faraday.default_connection
|
605
|
+
if options[:request]
|
606
|
+
if options[:request].kind_of?(Array)
|
607
|
+
method, uri, headers, body = options[:request]
|
608
|
+
elsif options[:request].kind_of?(Faraday::Request)
|
609
|
+
unless options[:connection]
|
610
|
+
raise ArgumentError,
|
611
|
+
"Faraday::Request used, requires a connection to be provided."
|
612
|
+
end
|
613
|
+
method = options[:request].method.to_s.downcase.to_sym
|
614
|
+
uri = options[:connection].build_url(
|
615
|
+
options[:request].path, options[:request].params
|
616
|
+
)
|
617
|
+
headers = options[:request].headers || {}
|
618
|
+
body = options[:request].body || ''
|
619
|
+
end
|
620
|
+
else
|
621
|
+
method = options[:method] || :get
|
622
|
+
uri = options[:uri]
|
623
|
+
headers = options[:headers] || []
|
624
|
+
body = options[:body] || ''
|
625
|
+
end
|
626
|
+
headers = headers.to_a if headers.kind_of?(Hash)
|
627
|
+
request_components = {
|
628
|
+
:method => method,
|
629
|
+
:uri => uri,
|
630
|
+
:headers => headers,
|
631
|
+
:body => body
|
632
|
+
}
|
633
|
+
# Verify that we have all pieces required to transmit an HTTP request
|
634
|
+
request_components.each do |(key, value)|
|
635
|
+
unless value
|
636
|
+
raise ArgumentError, "Missing :#{key} parameter."
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
if self.user_agent != nil
|
641
|
+
# If there's no User-Agent header, set one.
|
642
|
+
unless headers.kind_of?(Enumerable)
|
643
|
+
# We need to use some Enumerable methods, relying on the presence of
|
644
|
+
# the #each method.
|
645
|
+
class << headers
|
646
|
+
include Enumerable
|
647
|
+
end
|
648
|
+
end
|
649
|
+
if self.user_agent.kind_of?(String)
|
650
|
+
unless headers.any? { |k, v| k.downcase == 'User-Agent'.downcase }
|
651
|
+
headers = headers.to_a.insert(0, ['User-Agent', self.user_agent])
|
652
|
+
end
|
653
|
+
elsif self.user_agent != nil
|
654
|
+
raise TypeError,
|
655
|
+
"Expected User-Agent to be String, got #{self.user_agent.class}"
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
request = options[:connection].build_request(
|
660
|
+
method.to_s.downcase.to_sym
|
661
|
+
) do |req|
|
662
|
+
req.url(Addressable::URI.parse(uri).normalize.to_s)
|
663
|
+
req.headers = Faraday::Utils::Headers.new(headers)
|
664
|
+
req.body = body
|
665
|
+
end
|
666
|
+
request_env = request.to_env(options[:connection])
|
667
|
+
response = options[:connection].app.call(request_env)
|
668
|
+
return response
|
669
|
+
end
|
670
|
+
|
671
|
+
##
|
672
|
+
# Executes a request, wrapping it in a Result object.
|
673
|
+
#
|
674
|
+
# @param [Google::APIClient::BatchRequest, Hash, Array] params
|
675
|
+
# Either a Google::APIClient::BatchRequest, a Hash, or an Array.
|
676
|
+
#
|
677
|
+
# If a Google::APIClient::BatchRequest, no other parameters are expected.
|
678
|
+
#
|
679
|
+
# If a Hash, the below parameters are handled. If an Array, the
|
680
|
+
# parameters are assumed to be in the below order:
|
681
|
+
#
|
682
|
+
# - (Google::APIClient::Method, String) api_method:
|
683
|
+
# The method object or the RPC name of the method being executed.
|
684
|
+
# - (Hash, Array) parameters:
|
685
|
+
# The parameters to send to the method.
|
686
|
+
# - (String) body: The body of the request.
|
687
|
+
# - (Hash, Array) headers: The HTTP headers for the request.
|
688
|
+
# - (Hash) options: A set of options for the request, of which:
|
689
|
+
# - (String) :version (default: "v1") -
|
690
|
+
# The service version. Only used if `api_method` is a `String`.
|
691
|
+
# - (#generate_authenticated_request) :authorization (default: true) -
|
692
|
+
# The authorization mechanism for the response. Used only if
|
693
|
+
# `:authenticated` is `true`.
|
694
|
+
# - (TrueClass, FalseClass) :authenticated (default: true) -
|
695
|
+
# `true` if the request must be signed or somehow
|
696
|
+
# authenticated, `false` otherwise.
|
697
|
+
#
|
698
|
+
# @return [Google::APIClient::Result] The result from the API, nil if batch.
|
699
|
+
#
|
700
|
+
# @example
|
701
|
+
# result = client.execute(batch_request)
|
702
|
+
#
|
703
|
+
# @example
|
704
|
+
# result = client.execute(
|
705
|
+
# :api_method => 'plus.activities.list',
|
706
|
+
# :parameters => {'collection' => 'public', 'userId' => 'me'}
|
707
|
+
# )
|
708
|
+
#
|
709
|
+
# @see Google::APIClient#generate_request
|
710
|
+
def execute(*params)
|
711
|
+
if params.last.kind_of?(Google::APIClient::BatchRequest) &&
|
712
|
+
params.size == 1
|
713
|
+
batch = params.pop
|
714
|
+
options = batch.options
|
715
|
+
options[:connection] ||= Faraday.default_connection
|
716
|
+
http_request = batch.to_http_request
|
717
|
+
request = nil
|
718
|
+
|
719
|
+
if @authorization
|
720
|
+
method, uri, headers, body = http_request
|
721
|
+
method = method.to_s.downcase.to_sym
|
722
|
+
|
723
|
+
faraday_request = options[:connection].build_request(
|
724
|
+
method.to_s.downcase.to_sym
|
725
|
+
) do |req|
|
726
|
+
req.url(Addressable::URI.parse(uri).normalize.to_s)
|
727
|
+
req.headers = Faraday::Utils::Headers.new(headers)
|
728
|
+
req.body = body
|
729
|
+
end
|
730
|
+
|
731
|
+
request = {
|
732
|
+
:request => self.generate_authenticated_request(
|
733
|
+
:request => faraday_request,
|
734
|
+
:connection => options[:connection]
|
735
|
+
),
|
736
|
+
:connection => options[:connection]
|
737
|
+
}
|
738
|
+
else
|
739
|
+
request = {
|
740
|
+
:request => http_request,
|
741
|
+
:connection => options[:connection]
|
742
|
+
}
|
743
|
+
end
|
744
|
+
|
745
|
+
response = self.transmit(request)
|
746
|
+
batch.process_response(response)
|
747
|
+
return nil
|
748
|
+
else
|
749
|
+
# This block of code allows us to accept multiple parameter passing
|
750
|
+
# styles, and maintaining some backwards compatibility.
|
751
|
+
#
|
752
|
+
# Note: I'm extremely tempted to deprecate this style of execute call.
|
753
|
+
if params.last.respond_to?(:to_hash) && params.size == 1
|
754
|
+
options = params.pop
|
755
|
+
else
|
756
|
+
options = {}
|
757
|
+
end
|
758
|
+
|
759
|
+
options[:api_method] = params.shift if params.size > 0
|
760
|
+
options[:parameters] = params.shift if params.size > 0
|
761
|
+
options[:body] = params.shift if params.size > 0
|
762
|
+
options[:headers] = params.shift if params.size > 0
|
763
|
+
options[:client] = self
|
764
|
+
options[:connection] ||= Faraday.default_connection
|
765
|
+
reference = Google::APIClient::Reference.new(options)
|
766
|
+
request = self.generate_request(reference)
|
767
|
+
response = self.transmit(
|
768
|
+
:request => request,
|
769
|
+
:connection => options[:connection]
|
770
|
+
)
|
771
|
+
return Google::APIClient::Result.new(reference, request, response)
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
##
|
776
|
+
# Same as Google::APIClient#execute, but raises an exception if there was
|
777
|
+
# an error.
|
778
|
+
#
|
779
|
+
# @see Google::APIClient#execute
|
780
|
+
def execute!(*params)
|
781
|
+
result = self.execute(*params)
|
782
|
+
if result.error?
|
783
|
+
error_message = result.error_message
|
784
|
+
case result.response.status
|
785
|
+
when 400...500
|
786
|
+
exception_type = ClientError
|
787
|
+
error_message ||= "A client error has occurred."
|
788
|
+
when 500...600
|
789
|
+
exception_type = ServerError
|
790
|
+
error_message ||= "A server error has occurred."
|
791
|
+
else
|
792
|
+
exception_type = TransmissionError
|
793
|
+
error_message ||= "A transmission error has occurred."
|
794
|
+
end
|
795
|
+
raise exception_type, error_message
|
796
|
+
end
|
797
|
+
return result
|
798
|
+
end
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
require 'google/api_client/version'
|