diff --git a/bin/google-api b/bin/google-api index f39b7908e..26e711229 100755 --- a/bin/google-api +++ b/bin/google-api @@ -15,45 +15,13 @@ require 'faraday/utils' require 'webrick' require 'google/api_client/version' require 'google/api_client' +require 'google/api_client/auth/installed_app' ARGV.unshift('--help') if ARGV.empty? module Google class APIClient class CLI - # Used for oauth login - class OAuthVerifierServlet < WEBrick::HTTPServlet::AbstractServlet - attr_reader :verifier - - def do_GET(request, response) - $verifier ||= Addressable::URI.unencode_component( - request.request_uri.to_s[/\?.*oauth_verifier=([^&$]+)(&|$)/, 1] || - request.request_uri.to_s[/\?.*code=([^&$]+)(&|$)/, 1] - ) - response.status = WEBrick::HTTPStatus::RC_ACCEPTED - # This javascript will auto-close the tab after the - # verifier is obtained. - response.body = <<-HTML - - - - - - You may close this window. - - -HTML - # Eww, hack! - server = self.instance_variable_get('@server') - server.stop if server - end - end # Initialize with default parameter values def initialize(argv) @@ -164,8 +132,7 @@ HTML opts.separator( "\nAvailable commands:\n" + - " oauth-1-login Log a user into an API with OAuth 1.0a\n" + - " oauth-2-login Log a user into an API with OAuth 2.0 d10\n" + + " oauth-2-login Log a user into an API with OAuth 2.0\n" + " list List the methods available for an API\n" + " execute Execute a method on the API\n" + " irb Start an interactive client session" @@ -184,9 +151,7 @@ HTML end def client - require 'signet/oauth_1/client' require 'yaml' - require 'irb' config_file = File.expand_path('~/.google-api.yaml') authorization = nil if File.exist?(config_file) @@ -198,19 +163,14 @@ HTML authorization = config["mechanism"].to_sym end - client = Google::APIClient.new(:authorization => authorization) + client = Google::APIClient.new( + :application_name => 'Ruby CLI', + :application_version => Google::APIClient::VERSION::STRING, + :authorization => authorization) case authorization when :oauth_1 - if client.authorization && - !client.authorization.kind_of?(Signet::OAuth1::Client) - STDERR.puts( - "Unexpected authorization mechanism: " + - "#{client.authorization.class}" - ) - exit(1) - end - config = open(config_file, 'r') { |file| YAML.load(file.read) } + STDERR.puts('OAuth 1 is deprecated. Please reauthorize with OAuth 2.') client.authorization.client_credential_key = config["client_credential_key"] client.authorization.client_credential_secret = @@ -220,15 +180,6 @@ HTML client.authorization.token_credential_secret = config["token_credential_secret"] when :oauth_2 - if client.authorization && - !client.authorization.kind_of?(Signet::OAuth2::Client) - STDERR.puts( - "Unexpected authorization mechanism: " + - "#{client.authorization.class}" - ) - exit(1) - end - config = open(config_file, 'r') { |file| YAML.load(file.read) } client.authorization.scope = options[:scope] client.authorization.client_id = config["client_id"] client.authorization.client_secret = config["client_secret"] @@ -268,84 +219,14 @@ HTML end COMMANDS = [ - :oauth_1_login, :oauth_2_login, :list, :execute, :irb, - :fuzz ] - def oauth_1_login - require 'signet/oauth_1/client' - require 'launchy' - require 'yaml' - if options[:client_credential_key] && - options[:client_credential_secret] - config = { - "mechanism" => "oauth_1", - "scope" => options[:scope], - "client_credential_key" => options[:client_credential_key], - "client_credential_secret" => options[:client_credential_secret], - "token_credential_key" => nil, - "token_credential_secret" => nil - } - config_file = File.expand_path('~/.google-api.yaml') - open(config_file, 'w') { |file| file.write(YAML.dump(config)) } - exit(0) - else - $verifier = nil - server = WEBrick::HTTPServer.new( - :Port => OAUTH_SERVER_PORT, - :Logger => WEBrick::Log.new, - :AccessLog => WEBrick::Log.new - ) - server.logger.level = 0 - trap("INT") { server.shutdown } - - server.mount("/", OAuthVerifierServlet) - - oauth_client = Signet::OAuth1::Client.new( - :temporary_credential_uri => - 'https://www.google.com/accounts/OAuthGetRequestToken', - :authorization_uri => - 'https://www.google.com/accounts/OAuthAuthorizeToken', - :token_credential_uri => - 'https://www.google.com/accounts/OAuthGetAccessToken', - :client_credential_key => 'anonymous', - :client_credential_secret => 'anonymous', - :callback => "http://localhost:#{OAUTH_SERVER_PORT}/" - ) - oauth_client.fetch_temporary_credential!(:additional_parameters => { - :scope => options[:scope], - :xoauth_displayname => 'Google API Client' - }) - - # Launch browser - Launchy::Browser.run(oauth_client.authorization_uri.to_s) - - server.start - oauth_client.fetch_token_credential!(:verifier => $verifier) - config = { - "scope" => options[:scope], - "client_credential_key" => - oauth_client.client_credential_key, - "client_credential_secret" => - oauth_client.client_credential_secret, - "token_credential_key" => - oauth_client.token_credential_key, - "token_credential_secret" => - oauth_client.token_credential_secret - } - config_file = File.expand_path('~/.google-api.yaml') - open(config_file, 'w') { |file| file.write(YAML.dump(config)) } - exit(0) - end - end - def oauth_2_login require 'signet/oauth_2/client' - require 'launchy' require 'yaml' if !options[:client_credential_key] || !options[:client_credential_secret] @@ -365,45 +246,26 @@ HTML open(config_file, 'w') { |file| file.write(YAML.dump(config)) } exit(0) else - $verifier = nil - logger = WEBrick::Log.new - logger.level = 0 - server = WEBrick::HTTPServer.new( - :Port => OAUTH_SERVER_PORT, - :Logger => logger, - :AccessLog => logger - ) - trap("INT") { server.shutdown } - - server.mount("/", OAuthVerifierServlet) - - oauth_client = Signet::OAuth2::Client.new( - :authorization_uri => - 'https://www.google.com/accounts/o8/oauth2/authorization', - :token_credential_uri => - 'https://www.google.com/accounts/o8/oauth2/token', + flow = Google::APIClient::InstalledAppFlow.new( + :port => OAUTH_SERVER_PORT, :client_id => options[:client_credential_key], :client_secret => options[:client_credential_secret], - :redirect_uri => "http://localhost:#{OAUTH_SERVER_PORT}/", :scope => options[:scope] ) - - # Launch browser - Launchy.open(oauth_client.authorization_uri.to_s) - - server.start - oauth_client.code = $verifier - oauth_client.fetch_access_token! - config = { - "mechanism" => "oauth_2", - "scope" => options[:scope], - "client_id" => oauth_client.client_id, - "client_secret" => oauth_client.client_secret, - "access_token" => oauth_client.access_token, - "refresh_token" => oauth_client.refresh_token - } - config_file = File.expand_path('~/.google-api.yaml') - open(config_file, 'w') { |file| file.write(YAML.dump(config)) } + + oauth_client = flow.authorize + if oauth_client + config = { + "mechanism" => "oauth_2", + "scope" => options[:scope], + "client_id" => oauth_client.client_id, + "client_secret" => oauth_client.client_secret, + "access_token" => oauth_client.access_token, + "refresh_token" => oauth_client.refresh_token + } + config_file = File.expand_path('~/.google-api.yaml') + open(config_file, 'w') { |file| file.write(YAML.dump(config)) } + end exit(0) end end @@ -414,7 +276,7 @@ HTML STDERR.puts('No API name supplied.') exit(1) end - client = Google::APIClient.new(:authorization => nil) + #client = Google::APIClient.new(:authorization => nil) if options[:discovery_uri] if options[:api] && options[:version] client.register_discovery_uri( @@ -517,16 +379,6 @@ HTML IRB.start(__FILE__) end - def fuzz - STDERR.puts('API fuzzing not yet supported.') - if self.rpcname - # Fuzz just one method - else - # Fuzz the entire API - end - exit(1) - end - def help puts self.parser exit(0) diff --git a/lib/google/api_client/auth/installed_app.rb b/lib/google/api_client/auth/installed_app.rb new file mode 100644 index 000000000..4f7bf11a4 --- /dev/null +++ b/lib/google/api_client/auth/installed_app.rb @@ -0,0 +1,115 @@ +# Copyright 2010 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'webrick' +require 'launchy' + +module Google + class APIClient + + # Small helper for the sample apps for performing OAuth 2.0 flows from the command + # line or in any other installed app environment. + # + # @example + # + # client = Google::APIClient.new + # flow = Google::APIClient::InstalledAppFlow.new( + # :client_id => '691380668085.apps.googleusercontent.com', + # :client_secret => '..., + # :scope => 'https://www.googleapis.com/auth/drive' + # ) + # client.authorization = flow.authorize + # + class InstalledAppFlow + + RESPONSE_BODY = <<-HTML + + + + + You may close this window. + + HTML + + ## + # Configure the flow + # + # @param [Hash] options The configuration parameters for the client. + # @option options [Fixnum] :port + # Port to run the embedded server on. Defaults to 9292 + # @option options [String] :client_id + # A unique identifier issued to the client to identify itself to the + # authorization server. + # @option options [String] :client_secret + # A shared symmetric secret issued by the authorization server, + # which is used to authenticate the client. + # @option options [String] :scope + # The scope of the access request, expressed either as an Array + # or as a space-delimited String. + # + # @see Signet::OAuth2::Client + def initialize(options) + @port = options[:port] || 9292 + @authorization = Signet::OAuth2::Client.new({ + :authorization_uri => 'https://accounts.google.com/o/oauth2/auth', + :token_credential_uri => 'https://accounts.google.com/o/oauth2/token', + :redirect_uri => "http://localhost:#{@port}/"}.update(options) + ) + end + + ## + # Request authorization. Opens a browser and waits for response. + # + # @return [Signet::OAuth2::Client] + # Authorization instance, nil if user cancelled. + def authorize + auth = @authorization + + server = WEBrick::HTTPServer.new( + :Port => @port, + :BindAddress =>"localhost", + :Logger => WEBrick::Log.new(STDOUT, 0), + :AccessLog => [] + ) + trap("INT") { server.shutdown } + + server.mount_proc '/' do |req, res| + auth.code = req.query['code'] + if auth.code + auth.fetch_access_token! + end + res.status = WEBrick::HTTPStatus::RC_ACCEPTED + res.body = RESPONSE_BODY + server.stop + end + + Launchy.open(auth.authorization_uri.to_s) + server.start + if @authorization.access_token + return @authorization + else + return nil + end + end + end + + end +end +