From f78b4b565b95d4a0590a28ecd51294ae77f59e12 Mon Sep 17 00:00:00 2001 From: Joe Gregorio Date: Tue, 4 Jan 2011 09:26:14 -0500 Subject: [PATCH] Moved the common code for executing the OAuth 1.0 flow into the library and enhance it to run a local web server if possible. Updated all examples to use the new code. --- apiclient/ext/authtools.py | 140 ++++++++++++++++++++++ samples/buzz/three_legged_dance.py | 19 +-- samples/latitude/three_legged_dance.py | 20 +--- samples/moderator/three_legged_dance.py | 19 +-- samples/threadqueue/three_legged_dance.py | 19 +-- 5 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 apiclient/ext/authtools.py diff --git a/apiclient/ext/authtools.py b/apiclient/ext/authtools.py new file mode 100644 index 0000000..5398bd8 --- /dev/null +++ b/apiclient/ext/authtools.py @@ -0,0 +1,140 @@ +# Copyright (C) 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. + +"""Command-line tools for authenticating via OAuth 1.0 + +Do the OAuth 1.0 Three Legged Dance for +a command line application. Stores the generated +credentials in a common file that is used by +other example apps in the same directory. +""" + +__author__ = 'jcgregorio@google.com (Joe Gregorio)' +__all__ = ["run"] + +import BaseHTTPServer +import logging +import pickle +import socket +import sys + +from optparse import OptionParser +from apiclient.oauth import RequestError + +try: + from urlparse import parse_qsl +except ImportError: + from cgi import parse_qsl + + +class ClientRedirectServer(BaseHTTPServer.HTTPServer): + """A server to handle OAuth 1.0 redirects back to localhost. + + Waits for a single request and parses the query parameters + into query_params and then stops serving. + """ + query_params = {} + + +class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """A handler for OAuth 2.0 redirects back to localhost. + + Waits for a single request and parses the query parameters + into the servers query_params and then stops serving. + """ + + def do_GET(s): + """Handle a GET request + + Parses the query parameters and prints a message + if the flow has completed. Note that we can't detect + if an error occurred. + """ + s.send_response(200) + s.send_header("Content-type", "text/html") + s.end_headers() + query = s.path.split('?', 1)[-1] + query = dict(parse_qsl(query)) + s.server.query_params = query + s.wfile.write("Authentication Status") + s.wfile.write("

The authentication flow has completed.

") + s.wfile.write("") + + def log_message(self, format, *args): + """Do not log messages to stdout while running as command line program.""" + pass + + +def run(flow, filename): + """Core code for a command-line application. + """ + parser = OptionParser() + parser.add_option("-f", "--file", dest="filename", + default=filename, help="write credentials to FILE", metavar="FILE") + parser.add_option("-p", "--no_local_web_server", dest="localhost", + action="store_false", + default=True, + help="Do not run a web server on localhost to handle redirect URIs") + parser.add_option("-w", "--local_web_server", dest="localhost", + action="store_true", + default=True, + help="Run a web server on localhost to handle redirect URIs") + + (options, args) = parser.parse_args() + + host_name = 'localhost' + port_numbers = [8080, 8090] + if options.localhost: + server_class = BaseHTTPServer.HTTPServer + try: + port_number = port_numbers[0] + httpd = server_class((host_name, port_number), ClientRedirectHandler) + except socket.error: + port_number = port_numbers[1] + try: + httpd = server_class((host_name, port_number), ClientRedirectHandler) + except socket.error: + options.localhost = False + + if options.localhost: + oauth_callback = 'http://%s:%s/' % (host_name, port_number) + else: + oauth_callback = 'oob' + authorize_url = flow.step1_get_authorize_url(oauth_callback) + + print 'Go to the following link in your browser:' + print authorize_url + print + + if options.localhost: + httpd.handle_request() + if 'error' in httpd.query_params: + sys.exit('Authentication request was rejected.') + if 'oauth_verifier' in httpd.query_params: + code = httpd.query_params['oauth_verifier'] + else: + accepted = 'n' + while accepted.lower() == 'n': + accepted = raw_input('Have you authorized me? (y/n) ') + code = raw_input('What is the verification code? ').strip() + + try: + credentials = flow.step2_exchange(code) + except RequestError: + sys.exit('The authentication has failed.') + + f = open(filename, 'w') + f.write(pickle.dumps(credentials)) + f.close() + print "You have successfully authenticated." diff --git a/samples/buzz/three_legged_dance.py b/samples/buzz/three_legged_dance.py index 9972455..2dc26e5 100644 --- a/samples/buzz/three_legged_dance.py +++ b/samples/buzz/three_legged_dance.py @@ -24,8 +24,8 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' from apiclient.discovery import build from apiclient.oauth import FlowThreeLegged +from apiclient.ext.authtools import run -import pickle buzz_discovery = build("buzz", "v1").auth_discovery() @@ -37,19 +37,4 @@ flow = FlowThreeLegged(buzz_discovery, scope='https://www.googleapis.com/auth/buzz', xoauth_displayname='Google API Client Example App') -authorize_url = flow.step1_get_authorize_url() - -print 'Go to the following link in your browser:' -print authorize_url -print - -accepted = 'n' -while accepted.lower() == 'n': - accepted = raw_input('Have you authorized me? (y/n) ') -verification = raw_input('What is the verification code? ').strip() - -credentials = flow.step2_exchange(verification) - -f = open('buzz.dat', 'w') -f.write(pickle.dumps(credentials)) -f.close() +run(flow, 'buzz.dat') diff --git a/samples/latitude/three_legged_dance.py b/samples/latitude/three_legged_dance.py index 56f4f59..45595bc 100644 --- a/samples/latitude/three_legged_dance.py +++ b/samples/latitude/three_legged_dance.py @@ -24,8 +24,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' from apiclient.discovery import build from apiclient.oauth import FlowThreeLegged - -import pickle +from apiclient.ext.authtools import run moderator_discovery = build("latitude", "v1").auth_discovery() @@ -44,19 +43,4 @@ flow = FlowThreeLegged(moderator_discovery, granularity='city' ) -authorize_url = flow.step1_get_authorize_url() - -print 'Go to the following link in your browser:' -print authorize_url -print - -accepted = 'n' -while accepted.lower() == 'n': - accepted = raw_input('Have you authorized me? (y/n) ') -verification = raw_input('What is the verification code? ').strip() - -credentials = flow.step2_exchange(verification) - -f = open('latitude.dat', 'w') -f.write(pickle.dumps(credentials)) -f.close() +run(flow, 'latitude.dat') diff --git a/samples/moderator/three_legged_dance.py b/samples/moderator/three_legged_dance.py index fbc90ec..32e624d 100644 --- a/samples/moderator/three_legged_dance.py +++ b/samples/moderator/three_legged_dance.py @@ -24,8 +24,8 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' from apiclient.discovery import build from apiclient.oauth import FlowThreeLegged +from apiclient.ext.authtools import run -import pickle moderator_discovery = build("moderator", "v1").auth_discovery() @@ -38,19 +38,4 @@ flow = FlowThreeLegged(moderator_discovery, #scope='tag:google.com,2010:auth/moderator', xoauth_displayname='Google API Client Example App') -authorize_url = flow.step1_get_authorize_url() - -print 'Go to the following link in your browser:' -print authorize_url -print - -accepted = 'n' -while accepted.lower() == 'n': - accepted = raw_input('Have you authorized me? (y/n) ') -verification = raw_input('What is the verification code? ').strip() - -credentials = flow.step2_exchange(verification) - -f = open('moderator.dat', 'w') -f.write(pickle.dumps(credentials)) -f.close() +run(flow, 'moderator.dat') diff --git a/samples/threadqueue/three_legged_dance.py b/samples/threadqueue/three_legged_dance.py index b833e3a..12c495b 100644 --- a/samples/threadqueue/three_legged_dance.py +++ b/samples/threadqueue/three_legged_dance.py @@ -24,8 +24,8 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' from apiclient.discovery import build from apiclient.oauth import FlowThreeLegged +from apiclient.ext.authtools import run -import pickle moderator_discovery = build("moderator", "v1").auth_discovery() @@ -38,19 +38,4 @@ flow = FlowThreeLegged(moderator_discovery, #scope='tag:google.com,2010:auth/moderator', xoauth_displayname='Google API Client Example App') -authorize_url = flow.step1_get_authorize_url() - -print 'Go to the following link in your browser:' -print authorize_url -print - -accepted = 'n' -while accepted.lower() == 'n': - accepted = raw_input('Have you authorized me? (y/n) ') -verification = raw_input('What is the verification code? ').strip() - -credentials = flow.step2_exchange(verification) - -f = open('moderator.dat', 'w') -f.write(pickle.dumps(credentials)) -f.close() +run(flow, 'moderator.dat')