Add oauth helper for installed apps, update CLI
This commit is contained in:
parent
1d7315ee9b
commit
8ce4d052fe
194
bin/google-api
194
bin/google-api
|
@ -15,45 +15,13 @@ require 'faraday/utils'
|
||||||
require 'webrick'
|
require 'webrick'
|
||||||
require 'google/api_client/version'
|
require 'google/api_client/version'
|
||||||
require 'google/api_client'
|
require 'google/api_client'
|
||||||
|
require 'google/api_client/auth/installed_app'
|
||||||
|
|
||||||
ARGV.unshift('--help') if ARGV.empty?
|
ARGV.unshift('--help') if ARGV.empty?
|
||||||
|
|
||||||
module Google
|
module Google
|
||||||
class APIClient
|
class APIClient
|
||||||
class CLI
|
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
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script>
|
|
||||||
function closeWindow() {
|
|
||||||
window.open('', '_self', '');
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
setTimeout(closeWindow, 10);
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
You may close this window.
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
HTML
|
|
||||||
# Eww, hack!
|
|
||||||
server = self.instance_variable_get('@server')
|
|
||||||
server.stop if server
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Initialize with default parameter values
|
# Initialize with default parameter values
|
||||||
def initialize(argv)
|
def initialize(argv)
|
||||||
|
@ -164,8 +132,7 @@ HTML
|
||||||
|
|
||||||
opts.separator(
|
opts.separator(
|
||||||
"\nAvailable commands:\n" +
|
"\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\n" +
|
||||||
" oauth-2-login Log a user into an API with OAuth 2.0 d10\n" +
|
|
||||||
" list List the methods available for an API\n" +
|
" list List the methods available for an API\n" +
|
||||||
" execute Execute a method on the API\n" +
|
" execute Execute a method on the API\n" +
|
||||||
" irb Start an interactive client session"
|
" irb Start an interactive client session"
|
||||||
|
@ -184,9 +151,7 @@ HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
def client
|
def client
|
||||||
require 'signet/oauth_1/client'
|
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'irb'
|
|
||||||
config_file = File.expand_path('~/.google-api.yaml')
|
config_file = File.expand_path('~/.google-api.yaml')
|
||||||
authorization = nil
|
authorization = nil
|
||||||
if File.exist?(config_file)
|
if File.exist?(config_file)
|
||||||
|
@ -198,19 +163,14 @@ HTML
|
||||||
authorization = config["mechanism"].to_sym
|
authorization = config["mechanism"].to_sym
|
||||||
end
|
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
|
case authorization
|
||||||
when :oauth_1
|
when :oauth_1
|
||||||
if client.authorization &&
|
STDERR.puts('OAuth 1 is deprecated. Please reauthorize with OAuth 2.')
|
||||||
!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) }
|
|
||||||
client.authorization.client_credential_key =
|
client.authorization.client_credential_key =
|
||||||
config["client_credential_key"]
|
config["client_credential_key"]
|
||||||
client.authorization.client_credential_secret =
|
client.authorization.client_credential_secret =
|
||||||
|
@ -220,15 +180,6 @@ HTML
|
||||||
client.authorization.token_credential_secret =
|
client.authorization.token_credential_secret =
|
||||||
config["token_credential_secret"]
|
config["token_credential_secret"]
|
||||||
when :oauth_2
|
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.scope = options[:scope]
|
||||||
client.authorization.client_id = config["client_id"]
|
client.authorization.client_id = config["client_id"]
|
||||||
client.authorization.client_secret = config["client_secret"]
|
client.authorization.client_secret = config["client_secret"]
|
||||||
|
@ -268,84 +219,14 @@ HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
COMMANDS = [
|
COMMANDS = [
|
||||||
:oauth_1_login,
|
|
||||||
:oauth_2_login,
|
:oauth_2_login,
|
||||||
:list,
|
:list,
|
||||||
:execute,
|
:execute,
|
||||||
:irb,
|
: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
|
def oauth_2_login
|
||||||
require 'signet/oauth_2/client'
|
require 'signet/oauth_2/client'
|
||||||
require 'launchy'
|
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
if !options[:client_credential_key] ||
|
if !options[:client_credential_key] ||
|
||||||
!options[:client_credential_secret]
|
!options[:client_credential_secret]
|
||||||
|
@ -365,45 +246,26 @@ HTML
|
||||||
open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
|
open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
|
||||||
exit(0)
|
exit(0)
|
||||||
else
|
else
|
||||||
$verifier = nil
|
flow = Google::APIClient::InstalledAppFlow.new(
|
||||||
logger = WEBrick::Log.new
|
:port => OAUTH_SERVER_PORT,
|
||||||
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',
|
|
||||||
:client_id => options[:client_credential_key],
|
:client_id => options[:client_credential_key],
|
||||||
:client_secret => options[:client_credential_secret],
|
:client_secret => options[:client_credential_secret],
|
||||||
:redirect_uri => "http://localhost:#{OAUTH_SERVER_PORT}/",
|
|
||||||
:scope => options[:scope]
|
:scope => options[:scope]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Launch browser
|
oauth_client = flow.authorize
|
||||||
Launchy.open(oauth_client.authorization_uri.to_s)
|
if oauth_client
|
||||||
|
config = {
|
||||||
server.start
|
"mechanism" => "oauth_2",
|
||||||
oauth_client.code = $verifier
|
"scope" => options[:scope],
|
||||||
oauth_client.fetch_access_token!
|
"client_id" => oauth_client.client_id,
|
||||||
config = {
|
"client_secret" => oauth_client.client_secret,
|
||||||
"mechanism" => "oauth_2",
|
"access_token" => oauth_client.access_token,
|
||||||
"scope" => options[:scope],
|
"refresh_token" => oauth_client.refresh_token
|
||||||
"client_id" => oauth_client.client_id,
|
}
|
||||||
"client_secret" => oauth_client.client_secret,
|
config_file = File.expand_path('~/.google-api.yaml')
|
||||||
"access_token" => oauth_client.access_token,
|
open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
|
||||||
"refresh_token" => oauth_client.refresh_token
|
end
|
||||||
}
|
|
||||||
config_file = File.expand_path('~/.google-api.yaml')
|
|
||||||
open(config_file, 'w') { |file| file.write(YAML.dump(config)) }
|
|
||||||
exit(0)
|
exit(0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -414,7 +276,7 @@ HTML
|
||||||
STDERR.puts('No API name supplied.')
|
STDERR.puts('No API name supplied.')
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
client = Google::APIClient.new(:authorization => nil)
|
#client = Google::APIClient.new(:authorization => nil)
|
||||||
if options[:discovery_uri]
|
if options[:discovery_uri]
|
||||||
if options[:api] && options[:version]
|
if options[:api] && options[:version]
|
||||||
client.register_discovery_uri(
|
client.register_discovery_uri(
|
||||||
|
@ -517,16 +379,6 @@ HTML
|
||||||
IRB.start(__FILE__)
|
IRB.start(__FILE__)
|
||||||
end
|
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
|
def help
|
||||||
puts self.parser
|
puts self.parser
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
|
@ -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
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
function closeWindow() {
|
||||||
|
window.open('', '_self', '');
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
setTimeout(closeWindow, 10);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>You may close this window.</body>
|
||||||
|
</html>
|
||||||
|
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
|
||||||
|
|
Loading…
Reference in New Issue