3 bin_dir = File.expand_path("..", __FILE__)
4 lib_dir = File.expand_path("../lib", bin_dir)
6 $LOAD_PATH.unshift(lib_dir)
9 OAUTH_SERVER_PORT = 12736
14 gem 'faraday', '~> 0.8.1'
16 require 'faraday/utils'
19 require 'google/api_client/version'
20 require 'google/api_client'
22 ARGV.unshift('--help') if ARGV.empty?
27 # Used for oauth login
28 class OAuthVerifierServlet < WEBrick::HTTPServlet::AbstractServlet
31 def do_GET(request, response)
32 $verifier ||= Addressable::URI.unencode_component(
33 request.request_uri.to_s[/\?.*oauth_verifier=([^&$]+)(&|$)/, 1] ||
34 request.request_uri.to_s[/\?.*code=([^&$]+)(&|$)/, 1]
36 response.status = WEBrick::HTTPStatus::RC_ACCEPTED
37 # This javascript will auto-close the tab after the
38 # verifier is obtained.
39 response.body = <<-HTML
43 function closeWindow() {
44 window.open('', '_self', '');
47 setTimeout(closeWindow, 10);
51 You may close this window.
56 server = self.instance_variable_get('@server')
61 # Initialize with default parameter values
64 :command => 'execute',
69 if @argv.first =~ /^[a-z0-9][a-z0-9_-]*$/i
70 self.options[:command] = @argv.shift
72 if @argv.first =~ /^[a-z0-9_-]+\.[a-z0-9_\.-]+$/i
73 self.options[:rpcname] = @argv.shift
81 return self.options[:command]
85 return self.options[:rpcname]
89 @parser ||= OptionParser.new do |opts|
90 opts.banner = "Usage: google-api " +
91 "(execute <rpcname> | [command]) [options] [-- <parameters>]"
93 opts.separator "\nAvailable options:"
96 "--scope <scope>", String, "Set the OAuth scope") do |s|
100 "--client-id <key>", String,
101 "Set the OAuth client id or key") do |k|
102 options[:client_credential_key] = k
105 "--client-secret <secret>", String,
106 "Set the OAuth client secret") do |s|
107 options[:client_credential_secret] = s
110 "--api <name>", String,
111 "Perform discovery on API") do |s|
115 "--api-version <id>", String,
116 "Select api version") do |id|
117 options[:version] = id
120 "--content-type <format>", String,
121 "Content-Type for request") do |f|
122 # Resolve content type shortcuts
125 f = 'application/json'
127 f = 'application/xml'
129 f = 'application/atom+xml'
131 f = 'application/rss+xml'
133 options[:content_type] = f
136 "-u", "--uri <uri>", String,
137 "Sets the URI to perform a request against") do |u|
141 "--discovery-uri <uri>", String,
142 "Sets the URI to perform discovery") do |u|
143 options[:discovery_uri] = u
146 "-m", "--method <method>", String,
147 "Sets the HTTP method to use for the request") do |m|
148 options[:http_method] = m
151 "--requestor-id <email>", String,
152 "Sets the email address of the requestor") do |e|
153 options[:requestor_id] = e
156 opts.on("-v", "--verbose", "Run verbosely") do |v|
157 options[:verbose] = v
159 opts.on("-h", "--help", "Show this message") do
163 opts.on("--version", "Show version") do
164 puts "google-api-client (#{Google::APIClient::VERSION::STRING})"
169 "\nAvailable commands:\n" +
170 " oauth-1-login Log a user into an API with OAuth 1.0a\n" +
171 " oauth-2-login Log a user into an API with OAuth 2.0 d10\n" +
172 " list List the methods available for an API\n" +
173 " execute Execute a method on the API\n" +
174 " irb Start an interactive client session"
180 self.parser.parse!(self.argv)
181 symbol = self.command.gsub(/-/, "_").to_sym
182 if !COMMANDS.include?(symbol)
183 STDERR.puts("Invalid command: #{self.command}")
190 gem 'signet', '~> 0.4.0'
191 require 'signet/oauth_1/client'
194 config_file = File.expand_path('~/.google-api.yaml')
196 if File.exist?(config_file)
197 config = open(config_file, 'r') { |file| YAML.load(file.read) }
201 if config["mechanism"]
202 authorization = config["mechanism"].to_sym
205 client = Google::APIClient.new(:authorization => authorization)
209 if client.authorization &&
210 !client.authorization.kind_of?(Signet::OAuth1::Client)
212 "Unexpected authorization mechanism: " +
213 "#{client.authorization.class}"
217 config = open(config_file, 'r') { |file| YAML.load(file.read) }
218 client.authorization.client_credential_key =
219 config["client_credential_key"]
220 client.authorization.client_credential_secret =
221 config["client_credential_secret"]
222 client.authorization.token_credential_key =
223 config["token_credential_key"]
224 client.authorization.token_credential_secret =
225 config["token_credential_secret"]
227 if client.authorization &&
228 !client.authorization.kind_of?(Signet::OAuth2::Client)
230 "Unexpected authorization mechanism: " +
231 "#{client.authorization.class}"
235 config = open(config_file, 'r') { |file| YAML.load(file.read) }
236 client.authorization.scope = options[:scope]
237 client.authorization.client_id = config["client_id"]
238 client.authorization.client_secret = config["client_secret"]
239 client.authorization.access_token = config["access_token"]
240 client.authorization.refresh_token = config["refresh_token"]
245 if options[:discovery_uri]
246 if options[:api] && options[:version]
247 client.register_discovery_uri(
248 options[:api], options[:version], options[:discovery_uri]
252 'Cannot register a discovery URI without ' +
253 'specifying an API and version.'
262 def api_version(api_name, version)
265 if client.preferred_version(api_name)
266 v = client.preferred_version(api_name).version
284 gem 'signet', '~> 0.4.0'
285 require 'signet/oauth_1/client'
288 if options[:client_credential_key] &&
289 options[:client_credential_secret]
291 "mechanism" => "oauth_1",
292 "scope" => options[:scope],
293 "client_credential_key" => options[:client_credential_key],
294 "client_credential_secret" => options[:client_credential_secret],
295 "token_credential_key" => nil,
296 "token_credential_secret" => nil
298 config_file = File.expand_path('~/.google-api.yaml')
299 open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
303 server = WEBrick::HTTPServer.new(
304 :Port => OAUTH_SERVER_PORT,
305 :Logger => WEBrick::Log.new,
306 :AccessLog => WEBrick::Log.new
308 server.logger.level = 0
309 trap("INT") { server.shutdown }
311 server.mount("/", OAuthVerifierServlet)
313 oauth_client = Signet::OAuth1::Client.new(
314 :temporary_credential_uri =>
315 'https://www.google.com/accounts/OAuthGetRequestToken',
316 :authorization_uri =>
317 'https://www.google.com/accounts/OAuthAuthorizeToken',
318 :token_credential_uri =>
319 'https://www.google.com/accounts/OAuthGetAccessToken',
320 :client_credential_key => 'anonymous',
321 :client_credential_secret => 'anonymous',
322 :callback => "http://localhost:#{OAUTH_SERVER_PORT}/"
324 oauth_client.fetch_temporary_credential!(:additional_parameters => {
325 :scope => options[:scope],
326 :xoauth_displayname => 'Google API Client'
330 Launchy::Browser.run(oauth_client.authorization_uri.to_s)
333 oauth_client.fetch_token_credential!(:verifier => $verifier)
335 "scope" => options[:scope],
336 "client_credential_key" =>
337 oauth_client.client_credential_key,
338 "client_credential_secret" =>
339 oauth_client.client_credential_secret,
340 "token_credential_key" =>
341 oauth_client.token_credential_key,
342 "token_credential_secret" =>
343 oauth_client.token_credential_secret
345 config_file = File.expand_path('~/.google-api.yaml')
346 open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
352 gem 'signet', '~> 0.4.0'
353 require 'signet/oauth_2/client'
356 if !options[:client_credential_key] ||
357 !options[:client_credential_secret]
358 STDERR.puts('No client ID and secret supplied.')
361 if options[:access_token]
363 "mechanism" => "oauth_2",
364 "scope" => options[:scope],
365 "client_id" => options[:client_credential_key],
366 "client_secret" => options[:client_credential_secret],
367 "access_token" => options[:access_token],
368 "refresh_token" => options[:refresh_token]
370 config_file = File.expand_path('~/.google-api.yaml')
371 open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
375 logger = WEBrick::Log.new
377 server = WEBrick::HTTPServer.new(
378 :Port => OAUTH_SERVER_PORT,
382 trap("INT") { server.shutdown }
384 server.mount("/", OAuthVerifierServlet)
386 oauth_client = Signet::OAuth2::Client.new(
387 :authorization_uri =>
388 'https://www.google.com/accounts/o8/oauth2/authorization',
389 :token_credential_uri =>
390 'https://www.google.com/accounts/o8/oauth2/token',
391 :client_id => options[:client_credential_key],
392 :client_secret => options[:client_credential_secret],
393 :redirect_uri => "http://localhost:#{OAUTH_SERVER_PORT}/",
394 :scope => options[:scope]
398 Launchy.open(oauth_client.authorization_uri.to_s)
401 oauth_client.code = $verifier
402 oauth_client.fetch_access_token!
404 "mechanism" => "oauth_2",
405 "scope" => options[:scope],
406 "client_id" => oauth_client.client_id,
407 "client_secret" => oauth_client.client_secret,
408 "access_token" => oauth_client.access_token,
409 "refresh_token" => oauth_client.refresh_token
411 config_file = File.expand_path('~/.google-api.yaml')
412 open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
418 api_name = options[:api]
420 STDERR.puts('No API name supplied.')
423 client = Google::APIClient.new(:authorization => nil)
424 if options[:discovery_uri]
425 if options[:api] && options[:version]
426 client.register_discovery_uri(
427 options[:api], options[:version], options[:discovery_uri]
431 'Cannot register a discovery URI without ' +
432 'specifying an API and version.'
437 version = api_version(api_name, options[:version])
438 api = client.discovered_api(api_name, version)
439 rpcnames = api.to_h.keys
440 puts rpcnames.sort.join("\n")
447 # Setup HTTP request data
449 input_streams, _, _ = IO.select([STDIN], [], [], 0)
450 request_body = STDIN.read || '' if input_streams
452 if options[:content_type]
453 headers << ['Content-Type', options[:content_type]]
456 headers << ['Content-Type', 'application/json']
460 # Make request with URI manually specified
461 uri = Addressable::URI.parse(options[:uri])
463 STDERR.puts('URI may not be relative.')
466 if options[:requestor_id]
467 uri.query_values = uri.query_values.merge(
468 'xoauth_requestor_id' => options[:requestor_id]
471 method = options[:http_method]
472 method ||= request_body == '' ? 'GET' : 'POST'
474 request = [method, uri.to_str, headers, [request_body]]
475 request = client.generate_authenticated_request(:request => request)
476 response = client.transmit(request)
480 # Make request with URI generated from template and parameters
482 STDERR.puts('No rpcname supplied.')
485 api_name = options[:api] || self.rpcname[/^([^\.]+)\./, 1]
486 version = api_version(api_name, options[:version])
487 api = client.discovered_api(api_name, version)
488 method = api.to_h[self.rpcname]
491 "Method #{self.rpcname} does not exist for " +
492 "#{api_name}-#{version}."
496 parameters = self.argv.inject({}) do |accu, pair|
497 name, value = pair.split('=', 2)
501 if options[:requestor_id]
502 parameters['xoauth_requestor_id'] = options[:requestor_id]
505 result = client.execute(
506 :api_method => method,
507 :parameters => parameters,
508 :merged_body => request_body,
511 puts result.response.body
513 rescue ArgumentError => e
521 $client = self.client
522 # Otherwise IRB will misinterpret command-line options
528 STDERR.puts('API fuzzing not yet supported.')
530 # Fuzz just one method
532 # Fuzz the entire API
545 Google::APIClient::CLI.new(ARGV).parse!