Add oauth helper for installed apps, update CLI

This commit is contained in:
Steven Bazyl 2013-01-04 16:14:59 -08:00
parent 1d7315ee9b
commit 8ce4d052fe
2 changed files with 139 additions and 172 deletions

View File

@ -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
<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
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)

View File

@ -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