google-api-client 0.5.0 → 0.6.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.
@@ -1,3 +1,17 @@
1
+ # 0.6
2
+
3
+ * Apps strongly encouraged to set :application_name & :application_version when
4
+ initializing a client
5
+ * JWT/service accounts moved to signet
6
+ * Added helper class for installed app OAuth flows, updated samples & CLI
7
+ * Initial logging support for client
8
+ * Fix PKCS12 loading on windows
9
+ * Allow disabling auto-refresh of OAuth 2 access tokens
10
+ * Compatibility with MultiJson >= 1.0.0 & Rails 3.2.8
11
+ * Fix for body serialization when body doesn't respond to to_json
12
+ * Remove OAuth 1.0 logins from CLI
13
+
14
+
1
15
  # 0.5.0
2
16
 
3
17
  * Beta candidate, potential incompatible changes with how requests are processed.
data/Gemfile CHANGED
@@ -2,11 +2,11 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'signet', '>= 0.4.1'
5
+ gem 'signet', '>= 0.4.4'
6
6
  gem 'addressable', '>= 2.3.2'
7
7
  gem 'uuidtools', '>= 2.1.0'
8
8
  gem 'autoparse', '>= 0.3.2'
9
- gem 'faraday', '~> 0.8.1'
9
+ gem 'faraday', '~> 0.8.4'
10
10
  gem 'multi_json', '>= 1.0.0'
11
11
  gem 'extlib', '>= 0.9.15'
12
12
  gem 'jwt', '~> 0.1.5'
data/README.md CHANGED
@@ -80,16 +80,19 @@ Most interactions with Google APIs require users to authorize applications via O
80
80
 
81
81
  Credentials can be managed at the connection level, as shown, or supplied on a per-request basis when calling `execute`.
82
82
 
83
- For server-to-server interactions, like those between a web application and Google Cloud Storage, Prediction, or BigQuery APIs, use service accounts. Assertions for service accounts are made with `Google::APIClient::JWTAsserter`.
84
-
85
- client = Google::APIClient.new
86
- key = Google::APIClient::PKCS12.load_key('client.p12', 'notasecret')
87
- service_account = Google::APIClient::JWTAsserter(
88
- '123456-abcdef@developer.gserviceaccount.com',
89
- 'https://www.googleapis.com/auth/prediction',
90
- key)
91
- client.authorization = service_account.authorize
92
-
83
+ For server-to-server interactions, like those between a web application and Google Cloud Storage, Prediction, or BigQuery APIs, use service accounts.
84
+
85
+ key = Google::APIClient::KeyUtils.load_from_pkcs12('client.p12', 'notasecret')
86
+ client.authorization = Signet::OAuth2::Client.new(
87
+ :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
88
+ :audience => 'https://accounts.google.com/o/oauth2/token',
89
+ :scope => 'https://www.googleapis.com/auth/prediction',
90
+ :issuer => '123456-abcdef@developer.gserviceaccount.com',
91
+ :signing_key => key)
92
+ client.authorization.fetch_access_token!
93
+ client.execute(...)
94
+
95
+
93
96
  ### Batching Requests
94
97
 
95
98
  Some Google APIs support batching requests into a single HTTP request. Use `Google::APIClient::BatchRequest`
@@ -104,9 +107,9 @@ Example:
104
107
  puts result.data
105
108
  end
106
109
 
107
- batch.add(:api_method=>urlshortener.url.insert,
110
+ batch.add(:api_method => urlshortener.url.insert,
108
111
  :body_object => { 'longUrl' => 'http://example.com/foo' })
109
- batch.add(:api_method=>urlshortener.url.insert,
112
+ batch.add(:api_method => urlshortener.url.insert,
110
113
  :body_object => { 'longUrl' => 'http://example.com/bar' })
111
114
  client.execute(batch)
112
115
 
@@ -172,7 +175,7 @@ See the full list of [samples on Google Code](http://code.google.com/p/google-ap
172
175
 
173
176
  ## Support
174
177
 
175
- Please [report bugs at the project on Google Code](http://code.google.com/p/google-api-ruby-client/issues/entry). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-api) about the client or APIs on [StackOverflow](http://stackoverflow.com).
178
+ Please [report bugs at the project on Google Code](http://code.google.com/p/google-api-ruby-client/issues/entry). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-api-ruby-client) about the client or APIs on [StackOverflow](http://stackoverflow.com).
176
179
 
177
180
 
178
181
 
data/Rakefile CHANGED
@@ -23,11 +23,15 @@ The Google API Ruby Client makes it trivial to discover and access supported
23
23
  APIs.
24
24
  TEXT
25
25
 
26
- PKG_FILES = FileList[
26
+ list = FileList[
27
27
  'lib/**/*', 'spec/**/*', 'vendor/**/*',
28
28
  'tasks/**/*', 'website/**/*',
29
29
  '[A-Z]*', 'Rakefile'
30
- ].exclude(/database\.yml/).exclude(/[_\.]git$/)
30
+ ].exclude(/[_\.]git$/)
31
+ (open(".gitignore") { |file| file.read }).split("\n").each do |pattern|
32
+ list.exclude(pattern)
33
+ end
34
+ PKG_FILES = list
31
35
 
32
36
  RCOV_ENABLED = !!(RUBY_PLATFORM != 'java' && RUBY_VERSION =~ /^1\.8/)
33
37
  if RCOV_ENABLED
@@ -15,45 +15,13 @@ require 'faraday/utils'
15
15
  require 'webrick'
16
16
  require 'google/api_client/version'
17
17
  require 'google/api_client'
18
+ require 'google/api_client/auth/installed_app'
18
19
 
19
20
  ARGV.unshift('--help') if ARGV.empty?
20
21
 
21
22
  module Google
22
23
  class APIClient
23
24
  class CLI
24
- # Used for oauth login
25
- class OAuthVerifierServlet < WEBrick::HTTPServlet::AbstractServlet
26
- attr_reader :verifier
27
-
28
- def do_GET(request, response)
29
- $verifier ||= Addressable::URI.unencode_component(
30
- request.request_uri.to_s[/\?.*oauth_verifier=([^&$]+)(&|$)/, 1] ||
31
- request.request_uri.to_s[/\?.*code=([^&$]+)(&|$)/, 1]
32
- )
33
- response.status = WEBrick::HTTPStatus::RC_ACCEPTED
34
- # This javascript will auto-close the tab after the
35
- # verifier is obtained.
36
- response.body = <<-HTML
37
- <html>
38
- <head>
39
- <script>
40
- function closeWindow() {
41
- window.open('', '_self', '');
42
- window.close();
43
- }
44
- setTimeout(closeWindow, 10);
45
- </script>
46
- </head>
47
- <body>
48
- You may close this window.
49
- </body>
50
- </html>
51
- HTML
52
- # Eww, hack!
53
- server = self.instance_variable_get('@server')
54
- server.stop if server
55
- end
56
- end
57
25
 
58
26
  # Initialize with default parameter values
59
27
  def initialize(argv)
@@ -164,8 +132,7 @@ HTML
164
132
 
165
133
  opts.separator(
166
134
  "\nAvailable commands:\n" +
167
- " oauth-1-login Log a user into an API with OAuth 1.0a\n" +
168
- " oauth-2-login Log a user into an API with OAuth 2.0 d10\n" +
135
+ " oauth-2-login Log a user into an API with OAuth 2.0\n" +
169
136
  " list List the methods available for an API\n" +
170
137
  " execute Execute a method on the API\n" +
171
138
  " irb Start an interactive client session"
@@ -184,9 +151,7 @@ HTML
184
151
  end
185
152
 
186
153
  def client
187
- require 'signet/oauth_1/client'
188
154
  require 'yaml'
189
- require 'irb'
190
155
  config_file = File.expand_path('~/.google-api.yaml')
191
156
  authorization = nil
192
157
  if File.exist?(config_file)
@@ -198,19 +163,14 @@ HTML
198
163
  authorization = config["mechanism"].to_sym
199
164
  end
200
165
 
201
- client = Google::APIClient.new(:authorization => authorization)
166
+ client = Google::APIClient.new(
167
+ :application_name => 'Ruby CLI',
168
+ :application_version => Google::APIClient::VERSION::STRING,
169
+ :authorization => authorization)
202
170
 
203
171
  case authorization
204
172
  when :oauth_1
205
- if client.authorization &&
206
- !client.authorization.kind_of?(Signet::OAuth1::Client)
207
- STDERR.puts(
208
- "Unexpected authorization mechanism: " +
209
- "#{client.authorization.class}"
210
- )
211
- exit(1)
212
- end
213
- config = open(config_file, 'r') { |file| YAML.load(file.read) }
173
+ STDERR.puts('OAuth 1 is deprecated. Please reauthorize with OAuth 2.')
214
174
  client.authorization.client_credential_key =
215
175
  config["client_credential_key"]
216
176
  client.authorization.client_credential_secret =
@@ -220,15 +180,6 @@ HTML
220
180
  client.authorization.token_credential_secret =
221
181
  config["token_credential_secret"]
222
182
  when :oauth_2
223
- if client.authorization &&
224
- !client.authorization.kind_of?(Signet::OAuth2::Client)
225
- STDERR.puts(
226
- "Unexpected authorization mechanism: " +
227
- "#{client.authorization.class}"
228
- )
229
- exit(1)
230
- end
231
- config = open(config_file, 'r') { |file| YAML.load(file.read) }
232
183
  client.authorization.scope = options[:scope]
233
184
  client.authorization.client_id = config["client_id"]
234
185
  client.authorization.client_secret = config["client_secret"]
@@ -268,84 +219,14 @@ HTML
268
219
  end
269
220
 
270
221
  COMMANDS = [
271
- :oauth_1_login,
272
222
  :oauth_2_login,
273
223
  :list,
274
224
  :execute,
275
225
  :irb,
276
- :fuzz
277
226
  ]
278
227
 
279
- def oauth_1_login
280
- require 'signet/oauth_1/client'
281
- require 'launchy'
282
- require 'yaml'
283
- if options[:client_credential_key] &&
284
- options[:client_credential_secret]
285
- config = {
286
- "mechanism" => "oauth_1",
287
- "scope" => options[:scope],
288
- "client_credential_key" => options[:client_credential_key],
289
- "client_credential_secret" => options[:client_credential_secret],
290
- "token_credential_key" => nil,
291
- "token_credential_secret" => nil
292
- }
293
- config_file = File.expand_path('~/.google-api.yaml')
294
- open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
295
- exit(0)
296
- else
297
- $verifier = nil
298
- server = WEBrick::HTTPServer.new(
299
- :Port => OAUTH_SERVER_PORT,
300
- :Logger => WEBrick::Log.new,
301
- :AccessLog => WEBrick::Log.new
302
- )
303
- server.logger.level = 0
304
- trap("INT") { server.shutdown }
305
-
306
- server.mount("/", OAuthVerifierServlet)
307
-
308
- oauth_client = Signet::OAuth1::Client.new(
309
- :temporary_credential_uri =>
310
- 'https://www.google.com/accounts/OAuthGetRequestToken',
311
- :authorization_uri =>
312
- 'https://www.google.com/accounts/OAuthAuthorizeToken',
313
- :token_credential_uri =>
314
- 'https://www.google.com/accounts/OAuthGetAccessToken',
315
- :client_credential_key => 'anonymous',
316
- :client_credential_secret => 'anonymous',
317
- :callback => "http://localhost:#{OAUTH_SERVER_PORT}/"
318
- )
319
- oauth_client.fetch_temporary_credential!(:additional_parameters => {
320
- :scope => options[:scope],
321
- :xoauth_displayname => 'Google API Client'
322
- })
323
-
324
- # Launch browser
325
- Launchy::Browser.run(oauth_client.authorization_uri.to_s)
326
-
327
- server.start
328
- oauth_client.fetch_token_credential!(:verifier => $verifier)
329
- config = {
330
- "scope" => options[:scope],
331
- "client_credential_key" =>
332
- oauth_client.client_credential_key,
333
- "client_credential_secret" =>
334
- oauth_client.client_credential_secret,
335
- "token_credential_key" =>
336
- oauth_client.token_credential_key,
337
- "token_credential_secret" =>
338
- oauth_client.token_credential_secret
339
- }
340
- config_file = File.expand_path('~/.google-api.yaml')
341
- open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
342
- exit(0)
343
- end
344
- end
345
-
346
228
  def oauth_2_login
347
229
  require 'signet/oauth_2/client'
348
- require 'launchy'
349
230
  require 'yaml'
350
231
  if !options[:client_credential_key] ||
351
232
  !options[:client_credential_secret]
@@ -365,45 +246,26 @@ HTML
365
246
  open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
366
247
  exit(0)
367
248
  else
368
- $verifier = nil
369
- logger = WEBrick::Log.new
370
- logger.level = 0
371
- server = WEBrick::HTTPServer.new(
372
- :Port => OAUTH_SERVER_PORT,
373
- :Logger => logger,
374
- :AccessLog => logger
375
- )
376
- trap("INT") { server.shutdown }
377
-
378
- server.mount("/", OAuthVerifierServlet)
379
-
380
- oauth_client = Signet::OAuth2::Client.new(
381
- :authorization_uri =>
382
- 'https://www.google.com/accounts/o8/oauth2/authorization',
383
- :token_credential_uri =>
384
- 'https://www.google.com/accounts/o8/oauth2/token',
249
+ flow = Google::APIClient::InstalledAppFlow.new(
250
+ :port => OAUTH_SERVER_PORT,
385
251
  :client_id => options[:client_credential_key],
386
252
  :client_secret => options[:client_credential_secret],
387
- :redirect_uri => "http://localhost:#{OAUTH_SERVER_PORT}/",
388
253
  :scope => options[:scope]
389
254
  )
390
-
391
- # Launch browser
392
- Launchy.open(oauth_client.authorization_uri.to_s)
393
-
394
- server.start
395
- oauth_client.code = $verifier
396
- oauth_client.fetch_access_token!
397
- config = {
398
- "mechanism" => "oauth_2",
399
- "scope" => options[:scope],
400
- "client_id" => oauth_client.client_id,
401
- "client_secret" => oauth_client.client_secret,
402
- "access_token" => oauth_client.access_token,
403
- "refresh_token" => oauth_client.refresh_token
404
- }
405
- config_file = File.expand_path('~/.google-api.yaml')
406
- open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
255
+
256
+ oauth_client = flow.authorize
257
+ if oauth_client
258
+ config = {
259
+ "mechanism" => "oauth_2",
260
+ "scope" => options[:scope],
261
+ "client_id" => oauth_client.client_id,
262
+ "client_secret" => oauth_client.client_secret,
263
+ "access_token" => oauth_client.access_token,
264
+ "refresh_token" => oauth_client.refresh_token
265
+ }
266
+ config_file = File.expand_path('~/.google-api.yaml')
267
+ open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
268
+ end
407
269
  exit(0)
408
270
  end
409
271
  end
@@ -414,7 +276,7 @@ HTML
414
276
  STDERR.puts('No API name supplied.')
415
277
  exit(1)
416
278
  end
417
- client = Google::APIClient.new(:authorization => nil)
279
+ #client = Google::APIClient.new(:authorization => nil)
418
280
  if options[:discovery_uri]
419
281
  if options[:api] && options[:version]
420
282
  client.register_discovery_uri(
@@ -517,16 +379,6 @@ HTML
517
379
  IRB.start(__FILE__)
518
380
  end
519
381
 
520
- def fuzz
521
- STDERR.puts('API fuzzing not yet supported.')
522
- if self.rpcname
523
- # Fuzz just one method
524
- else
525
- # Fuzz the entire API
526
- end
527
- exit(1)
528
- end
529
-
530
382
  def help
531
383
  puts self.parser
532
384
  exit(0)
@@ -1,6 +1,9 @@
1
1
  require 'multi_json'
2
2
 
3
- if !MultiJson.respond_to?(:load) || MultiJson.method(:load).owner == Kernel
3
+ if !MultiJson.respond_to?(:load) || [
4
+ Kernel,
5
+ defined?(ActiveSupport::Dependencies::Loadable) && ActiveSupport::Dependencies::Loadable
6
+ ].compact.include?(MultiJson.method(:load).owner)
4
7
  module MultiJson
5
8
  class <<self
6
9
  alias :load :decode
@@ -20,6 +20,7 @@ require 'compat/multi_json'
20
20
  require 'stringio'
21
21
 
22
22
  require 'google/api_client/version'
23
+ require 'google/api_client/logging'
23
24
  require 'google/api_client/errors'
24
25
  require 'google/api_client/environment'
25
26
  require 'google/api_client/discovery'
@@ -29,11 +30,15 @@ require 'google/api_client/result'
29
30
  require 'google/api_client/media'
30
31
  require 'google/api_client/service_account'
31
32
  require 'google/api_client/batch'
33
+ require 'google/api_client/railtie' if defined?(Rails)
32
34
 
33
35
  module Google
36
+
34
37
  ##
35
38
  # This class manages APIs communication.
36
39
  class APIClient
40
+ include Google::APIClient::Logging
41
+
37
42
  ##
38
43
  # Creates a new Google API client.
39
44
  #
@@ -47,6 +52,10 @@ module Google
47
52
  # <li><code>:oauth_1</code></li>
48
53
  # <li><code>:oauth_2</code></li>
49
54
  # </ul>
55
+ # @option options [Boolean] :auto_refresh_token (true)
56
+ # The setting that controls whether or not the api client attempts to
57
+ # refresh authorization when a 401 is hit in #execute. If the token does
58
+ # not support it, this option is ignored.
50
59
  # @option options [String] :application_name
51
60
  # The name of the application using the client.
52
61
  # @option options [String] :application_version
@@ -62,6 +71,8 @@ module Google
62
71
  # @option options [String] :discovery_path ("/discovery/v1")
63
72
  # The discovery base path. This rarely needs to be changed.
64
73
  def initialize(options={})
74
+ logger.debug { "#{self.class} - Initializing client with options #{options}" }
75
+
65
76
  # Normalize key to String to allow indifferent access.
66
77
  options = options.inject({}) do |accu, (key, value)|
67
78
  accu[key.to_sym] = value
@@ -74,26 +85,29 @@ module Google
74
85
 
75
86
  # Most developers will want to leave this value alone and use the
76
87
  # application_name option.
77
- application_string = (
78
- options[:application_name] ? (
79
- "#{options[:application_name]}/" +
80
- "#{options[:application_version] || '0.0.0'}"
81
- ) : ""
82
- )
88
+ if options[:application_name]
89
+ app_name = options[:application_name]
90
+ app_version = options[:application_version]
91
+ application_string = "#{app_name}/#{app_version || '0.0.0'}"
92
+ else
93
+ logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
94
+ end
83
95
  self.user_agent = options[:user_agent] || (
84
96
  "#{application_string} " +
85
- "google-api-ruby-client/#{VERSION::STRING} " +
97
+ "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} " +
86
98
  ENV::OS_VERSION
87
99
  ).strip
88
100
  # The writer method understands a few Symbols and will generate useful
89
101
  # default authentication mechanisms.
90
102
  self.authorization =
91
103
  options.key?(:authorization) ? options[:authorization] : :oauth_2
104
+ self.auto_refresh_token = options.fetch(:auto_refresh_token) { true }
92
105
  self.key = options[:key]
93
106
  self.user_ip = options[:user_ip]
94
107
  @discovery_uris = {}
95
108
  @discovery_documents = {}
96
109
  @discovered_apis = {}
110
+
97
111
  return self
98
112
  end
99
113
 
@@ -153,6 +167,13 @@ module Google
153
167
  return @authorization
154
168
  end
155
169
 
170
+ ##
171
+ # The setting that controls whether or not the api client attempts to
172
+ # refresh authorization when a 401 is hit in #execute.
173
+ #
174
+ # @return [Boolean]
175
+ attr_accessor :auto_refresh_token
176
+
156
177
  ##
157
178
  # The application's API key issued by the API console.
158
179
  #
@@ -540,8 +561,9 @@ module Google
540
561
  request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
541
562
 
542
563
  result = request.send(connection)
543
- if result.status == 401 && authorization.respond_to?(:refresh_token)
564
+ if result.status == 401 && authorization.respond_to?(:refresh_token) && auto_refresh_token
544
565
  begin
566
+ logger.debug("Attempting refresh of access token & retry of request")
545
567
  authorization.fetch_access_token!
546
568
  result = request.send(connection)
547
569
  rescue Signet::AuthorizationError