google-api-client 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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