From 89dde4ec94d3aabf1d6ec75f7a37e68b4cbd0ea2 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 07:49:20 -0700 Subject: [PATCH 01/69] Updating to Python3 (print statements to functions) --- oauth2client/client.py | 4 ++-- oauth2client/locked_file.py | 4 ++-- oauth2client/old_run.py | 44 ++++++++++++++++++------------------- oauth2client/tools.py | 44 ++++++++++++++++++------------------- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index ad3044e..0531888 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -924,7 +924,7 @@ class GoogleCredentials(OAuth2Credentials): request = service.instances().list(project=PROJECT, zone=ZONE) response = request.execute() - print response + print(response) A service that does not require authentication does not need credentials @@ -938,7 +938,7 @@ class GoogleCredentials(OAuth2Credentials): request = service.apis().list() response = request.execute() - print response + print(response) """ diff --git a/oauth2client/locked_file.py b/oauth2client/locked_file.py index 31514dc..e1dbc13 100644 --- a/oauth2client/locked_file.py +++ b/oauth2client/locked_file.py @@ -21,10 +21,10 @@ Usage: f = LockedFile('filename', 'r+b', 'rb') f.open_and_lock() if f.is_locked(): - print 'Acquired filename with r+b mode' + print('Acquired filename with r+b mode') f.file_handle().write('locked data') else: - print 'Aquired filename with rb mode' + print('Aquired filename with rb mode') f.unlock_and_close() """ diff --git a/oauth2client/old_run.py b/oauth2client/old_run.py index da23358..7d0476f 100644 --- a/oauth2client/old_run.py +++ b/oauth2client/old_run.py @@ -103,13 +103,13 @@ def run(flow, storage, http=None): break FLAGS.auth_local_webserver = success if not success: - print 'Failed to start a local webserver listening on either port 8080' - print 'or port 9090. Please check your firewall settings and locally' - print 'running programs that may be blocking or using those ports.' - print - print 'Falling back to --noauth_local_webserver and continuing with', - print 'authorization.' - print + print('Failed to start a local webserver listening on either port 8080') + print('or port 9090. Please check your firewall settings and locally') + print('running programs that may be blocking or using those ports.') + print() + print('Falling back to --noauth_local_webserver and continuing with') + print('authorization.') + print() if FLAGS.auth_local_webserver: oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number) @@ -120,20 +120,20 @@ def run(flow, storage, http=None): if FLAGS.auth_local_webserver: webbrowser.open(authorize_url, new=1, autoraise=True) - print 'Your browser has been opened to visit:' - print - print ' ' + authorize_url - print - print 'If your browser is on a different machine then exit and re-run' - print 'this application with the command-line parameter ' - print - print ' --noauth_local_webserver' - print + print('Your browser has been opened to visit:') + print() + print(' ' + authorize_url) + print() + print('If your browser is on a different machine then exit and re-run') + print('this application with the command-line parameter ') + print() + print(' --noauth_local_webserver') + print() else: - print 'Go to the following link in your browser:' - print - print ' ' + authorize_url - print + print('Go to the following link in your browser:') + print() + print(' ' + authorize_url) + print() code = None if FLAGS.auth_local_webserver: @@ -143,7 +143,7 @@ def run(flow, storage, http=None): if 'code' in httpd.query_params: code = httpd.query_params['code'] else: - print 'Failed to find "code" in the query parameters of the redirect.' + print('Failed to find "code" in the query parameters of the redirect.') sys.exit('Try running with --noauth_local_webserver.') else: code = raw_input('Enter verification code: ').strip() @@ -155,6 +155,6 @@ def run(flow, storage, http=None): storage.put(credential) credential.set_store(storage) - print 'Authentication successful.' + print('Authentication successful.') return credential diff --git a/oauth2client/tools.py b/oauth2client/tools.py index 8b3cd12..0a1b135 100644 --- a/oauth2client/tools.py +++ b/oauth2client/tools.py @@ -170,13 +170,13 @@ def run_flow(flow, storage, flags, http=None): break flags.noauth_local_webserver = not success if not success: - print 'Failed to start a local webserver listening on either port 8080' - print 'or port 9090. Please check your firewall settings and locally' - print 'running programs that may be blocking or using those ports.' - print - print 'Falling back to --noauth_local_webserver and continuing with', - print 'authorization.' - print + print('Failed to start a local webserver listening on either port 8080') + print('or port 9090. Please check your firewall settings and locally') + print('running programs that may be blocking or using those ports.') + print() + print('Falling back to --noauth_local_webserver and continuing with') + print('authorization.') + print() if not flags.noauth_local_webserver: oauth_callback = 'http://%s:%s/' % (flags.auth_host_name, port_number) @@ -187,20 +187,20 @@ def run_flow(flow, storage, flags, http=None): if not flags.noauth_local_webserver: webbrowser.open(authorize_url, new=1, autoraise=True) - print 'Your browser has been opened to visit:' - print - print ' ' + authorize_url - print - print 'If your browser is on a different machine then exit and re-run this' - print 'application with the command-line parameter ' - print - print ' --noauth_local_webserver' - print + print('Your browser has been opened to visit:') + print() + print(' ' + authorize_url) + print() + print('If your browser is on a different machine then exit and re-run this') + print('application with the command-line parameter ') + print() + print(' --noauth_local_webserver') + print() else: - print 'Go to the following link in your browser:' - print - print ' ' + authorize_url - print + print('Go to the following link in your browser:') + print() + print(' ' + authorize_url) + print() code = None if not flags.noauth_local_webserver: @@ -210,7 +210,7 @@ def run_flow(flow, storage, flags, http=None): if 'code' in httpd.query_params: code = httpd.query_params['code'] else: - print 'Failed to find "code" in the query parameters of the redirect.' + print('Failed to find "code" in the query parameters of the redirect.') sys.exit('Try running with --noauth_local_webserver.') else: code = raw_input('Enter verification code: ').strip() @@ -222,7 +222,7 @@ def run_flow(flow, storage, flags, http=None): storage.put(credential) credential.set_store(storage) - print 'Authentication successful.' + print('Authentication successful.') return credential From 77bfef34de48b8c20e8f42519d95cd3420c03abd Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 08:01:11 -0700 Subject: [PATCH 02/69] Updating to Python3 - updated except format --- oauth2client/appengine.py | 2 +- oauth2client/gce.py | 2 +- oauth2client/locked_file.py | 16 ++++++++-------- oauth2client/old_run.py | 4 ++-- oauth2client/tools.py | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/oauth2client/appengine.py b/oauth2client/appengine.py index 7c7feab..a3cd5c8 100644 --- a/oauth2client/appengine.py +++ b/oauth2client/appengine.py @@ -193,7 +193,7 @@ class AppAssertionCredentials(AssertionCredentials): scopes = self.scope.split() (token, _) = app_identity.get_access_token( scopes, service_account_id=self.service_account_id) - except app_identity.Error, e: + except app_identity.Error as e: raise AccessTokenRefreshError(str(e)) self.access_token = token diff --git a/oauth2client/gce.py b/oauth2client/gce.py index c9ff8f4..03ff40d 100644 --- a/oauth2client/gce.py +++ b/oauth2client/gce.py @@ -84,7 +84,7 @@ class AppAssertionCredentials(AssertionCredentials): if response.status == 200: try: d = simplejson.loads(content) - except StandardError, e: + except StandardError as e: raise AccessTokenRefreshError(str(e)) self.access_token = d['accessToken'] else: diff --git a/oauth2client/locked_file.py b/oauth2client/locked_file.py index e1dbc13..f71d931 100644 --- a/oauth2client/locked_file.py +++ b/oauth2client/locked_file.py @@ -1,6 +1,6 @@ # Copyright 2011 Google Inc. # -# Licensed under the Apache License, Version 2.0 (the "License"); +# Licensed under the Ap ache 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 # @@ -122,7 +122,7 @@ class _PosixOpener(_Opener): validate_file(self._filename) try: self._fh = open(self._filename, self._mode) - except IOError, e: + except IOError as e: # If we can't access with _mode, try _fallback_mode and don't lock. if e.errno == errno.EACCES: self._fh = open(self._filename, self._fallback_mode) @@ -137,7 +137,7 @@ class _PosixOpener(_Opener): self._locked = True break - except OSError, e: + except OSError as e: if e.errno != errno.EEXIST: raise if (time.time() - start_time) >= timeout: @@ -192,7 +192,7 @@ try: validate_file(self._filename) try: self._fh = open(self._filename, self._mode) - except IOError, e: + except IOError as e: # If we can't access with _mode, try _fallback_mode and don't lock. if e.errno == errno.EACCES: self._fh = open(self._filename, self._fallback_mode) @@ -204,7 +204,7 @@ try: fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX) self._locked = True return - except IOError, e: + except IOError as e: # If not retrying, then just pass on the error. if timeout == 0: raise e @@ -267,7 +267,7 @@ try: validate_file(self._filename) try: self._fh = open(self._filename, self._mode) - except IOError, e: + except IOError as e: # If we can't access with _mode, try _fallback_mode and don't lock. if e.errno == errno.EACCES: self._fh = open(self._filename, self._fallback_mode) @@ -284,7 +284,7 @@ try: pywintypes.OVERLAPPED()) self._locked = True return - except pywintypes.error, e: + except pywintypes.error as e: if timeout == 0: raise e @@ -308,7 +308,7 @@ try: try: hfile = win32file._get_osfhandle(self._fh.fileno()) win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED()) - except pywintypes.error, e: + except pywintypes.error as e: if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR: raise self._locked = False diff --git a/oauth2client/old_run.py b/oauth2client/old_run.py index 7d0476f..36bbdce 100644 --- a/oauth2client/old_run.py +++ b/oauth2client/old_run.py @@ -96,7 +96,7 @@ def run(flow, storage, http=None): try: httpd = ClientRedirectServer((FLAGS.auth_host_name, port), ClientRedirectHandler) - except socket.error, e: + except socket.error as e: pass else: success = True @@ -150,7 +150,7 @@ def run(flow, storage, http=None): try: credential = flow.step2_exchange(code, http=http) - except client.FlowExchangeError, e: + except client.FlowExchangeError as e: sys.exit('Authentication has failed: %s' % e) storage.put(credential) diff --git a/oauth2client/tools.py b/oauth2client/tools.py index 0a1b135..dd3b128 100644 --- a/oauth2client/tools.py +++ b/oauth2client/tools.py @@ -163,7 +163,7 @@ def run_flow(flow, storage, flags, http=None): try: httpd = ClientRedirectServer((flags.auth_host_name, port), ClientRedirectHandler) - except socket.error, e: + except socket.error: pass else: success = True @@ -217,7 +217,7 @@ def run_flow(flow, storage, flags, http=None): try: credential = flow.step2_exchange(code, http=http) - except client.FlowExchangeError, e: + except client.FlowExchangeError as e: sys.exit('Authentication has failed: %s' % e) storage.put(credential) From b706901a2228cea1a8c5a2097392a6b19c9a1e71 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 08:08:13 -0700 Subject: [PATCH 03/69] Updating to Python3 - relative import: implicit to explicit --- oauth2client/clientsecrets.py | 2 +- oauth2client/crypt.py | 2 +- oauth2client/file.py | 6 +++--- oauth2client/keyring_storage.py | 4 ++-- oauth2client/multistore_file.py | 4 ++-- oauth2client/old_run.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py index ac99aae..ad53a83 100644 --- a/oauth2client/clientsecrets.py +++ b/oauth2client/clientsecrets.py @@ -21,7 +21,7 @@ an OAuth 2.0 protected service. __author__ = 'jcgregorio@google.com (Joe Gregorio)' -from anyjson import simplejson +from oauth2client.anyjson import simplejson # Properties that make a client_secrets.json file valid. TYPE_WEB = 'web' diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index af3cf76..4d9f1c0 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -20,7 +20,7 @@ import hashlib import logging import time -from anyjson import simplejson +from oauth2client.anyjson import simplejson CLOCK_SKEW_SECS = 300 # 5 minutes in seconds diff --git a/oauth2client/file.py b/oauth2client/file.py index 1895f94..8a19570 100644 --- a/oauth2client/file.py +++ b/oauth2client/file.py @@ -24,9 +24,9 @@ import os import stat import threading -from anyjson import simplejson -from client import Storage as BaseStorage -from client import Credentials +from oauth2client.anyjson import simplejson +from oauth2client.client import Storage as BaseStorage +from oauth2client.client import Credentials class CredentialsFileSymbolicLinkError(Exception): diff --git a/oauth2client/keyring_storage.py b/oauth2client/keyring_storage.py index efe2949..9ec9cdd 100644 --- a/oauth2client/keyring_storage.py +++ b/oauth2client/keyring_storage.py @@ -22,8 +22,8 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import keyring import threading -from client import Storage as BaseStorage -from client import Credentials +from oauth2client.client import Storage as BaseStorage +from oauth2client.client import Credentials class Storage(BaseStorage): diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py index ce7a519..1fc0250 100644 --- a/oauth2client/multistore_file.py +++ b/oauth2client/multistore_file.py @@ -49,11 +49,11 @@ import logging import os import threading -from anyjson import simplejson +from oauth2client.anyjson import simplejson from oauth2client.client import Storage as BaseStorage from oauth2client.client import Credentials from oauth2client import util -from locked_file import LockedFile +from oauth2client.locked_file import LockedFile logger = logging.getLogger(__name__) diff --git a/oauth2client/old_run.py b/oauth2client/old_run.py index 36bbdce..aee048c 100644 --- a/oauth2client/old_run.py +++ b/oauth2client/old_run.py @@ -25,8 +25,8 @@ import gflags from oauth2client import client from oauth2client import util -from tools import ClientRedirectHandler -from tools import ClientRedirectServer +from oauth2client.tools import ClientRedirectHandler +from oauth2client.tools import ClientRedirectServer FLAGS = gflags.FLAGS From 6c39b9be58b2e01f37688170b678c8f34deb7a9e Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 08:14:21 -0700 Subject: [PATCH 04/69] Updating to Python3 - octal literal: added 0o prefix --- oauth2client/file.py | 2 +- oauth2client/multistore_file.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/oauth2client/file.py b/oauth2client/file.py index 8a19570..7c84b2f 100644 --- a/oauth2client/file.py +++ b/oauth2client/file.py @@ -92,7 +92,7 @@ class Storage(BaseStorage): simple version of "touch" to ensure the file has been created. """ if not os.path.exists(self._filename): - old_umask = os.umask(0177) + old_umask = os.umask(0o177) try: open(self._filename, 'a+b').close() finally: diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py index 1fc0250..ba3d530 100644 --- a/oauth2client/multistore_file.py +++ b/oauth2client/multistore_file.py @@ -271,7 +271,7 @@ class _MultiStore(object): simple version of "touch" to ensure the file has been created. """ if not os.path.exists(self._file.filename()): - old_umask = os.umask(0177) + old_umask = os.umask(0o177) try: open(self._file.filename(), 'a+b').close() finally: From 2d27f08b2a14c9d25c8f62389c8163f03fbaaf86 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 08:24:27 -0700 Subject: [PATCH 05/69] Updating to Python3 - file() -> open() --- oauth2client/clientsecrets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py index ad53a83..2c55dc0 100644 --- a/oauth2client/clientsecrets.py +++ b/oauth2client/clientsecrets.py @@ -98,7 +98,7 @@ def loads(s): def _loadfile(filename): try: - fp = file(filename, 'r') + fp = open(filename, 'r') try: obj = simplejson.load(fp) finally: From f655235978d9fe57391f895cab6e45d05bd1b568 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 08:36:41 -0700 Subject: [PATCH 06/69] Updating to Python3 - StandardError -> (TypeError, ValueError) --- oauth2client/client.py | 6 +++--- oauth2client/gce.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 0531888..c31537f 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -755,7 +755,7 @@ class OAuth2Credentials(Credentials): self.invalid = True if self.store: self.store.locked_put(self) - except StandardError: + except (TypeError, ValueError): pass raise AccessTokenRefreshError(error_msg) @@ -792,7 +792,7 @@ class OAuth2Credentials(Credentials): d = simplejson.loads(content) if 'error' in d: error_msg = d['error'] - except StandardError: + except (TypeError, ValueError): pass raise TokenRevokeError(error_msg) @@ -1403,7 +1403,7 @@ def _parse_exchange_token_response(content): resp = {} try: resp = simplejson.loads(content) - except StandardError: + except (TypeError, ValueError): # different JSON libs raise different exceptions, # so we just do a catch-all here resp = dict(parse_qsl(content)) diff --git a/oauth2client/gce.py b/oauth2client/gce.py index 03ff40d..e9f7942 100644 --- a/oauth2client/gce.py +++ b/oauth2client/gce.py @@ -84,7 +84,7 @@ class AppAssertionCredentials(AssertionCredentials): if response.status == 200: try: d = simplejson.loads(content) - except StandardError as e: + except (TypeError, ValueError) as e: raise AccessTokenRefreshError(str(e)) self.access_token = d['accessToken'] else: From a23368f31170e4d87d03f97d32e8d395a80c3204 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 09:09:38 -0700 Subject: [PATCH 07/69] Updating to Python3 - Updating imports for urllib and urlparse. --- oauth2client/client.py | 29 ++++++++++++++++------------- oauth2client/tools.py | 7 +++++-- oauth2client/util.py | 18 ++++++++++-------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index c31537f..01b111f 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -28,8 +28,16 @@ import logging import os import sys import time -import urllib -import urlparse +try: + from urllib.parse import urlparse, urlunparse, urlencode, parse_qsl +except ImportError: + from urlparse import urlparse, urlunparse + from urllib import urlencode + try: + from urlparse import parse_qsl + except ImportError: + from cgi import parse_qsl + from collections import namedtuple from oauth2client import GOOGLE_AUTH_URI @@ -48,11 +56,6 @@ try: except ImportError: pass -try: - from urlparse import parse_qsl -except ImportError: - from cgi import parse_qsl - logger = logging.getLogger(__name__) # Expiry is stored in RFC3339 UTC format @@ -390,11 +393,11 @@ def _update_query_params(uri, params): Returns: The same URI but with the new query parameters added. """ - parts = list(urlparse.urlparse(uri)) + parts = list(urlparse(uri)) query_params = dict(parse_qsl(parts[4])) # 4 is the index of the query part query_params.update(params) - parts[4] = urllib.urlencode(query_params) - return urlparse.urlunparse(parts) + parts[4] = urlencode(query_params) + return urlunparse(parts) class OAuth2Credentials(Credentials): @@ -666,7 +669,7 @@ class OAuth2Credentials(Credentials): def _generate_refresh_request_body(self): """Generate the body that will be used in the refresh request.""" - body = urllib.urlencode({ + body = urlencode({ 'grant_type': 'refresh_token', 'client_id': self.client_id, 'client_secret': self.client_secret, @@ -1206,7 +1209,7 @@ class AssertionCredentials(GoogleCredentials): def _generate_refresh_request_body(self): assertion = self._generate_assertion() - body = urllib.urlencode({ + body = urlencode({ 'assertion': assertion, 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', }) @@ -1614,7 +1617,7 @@ class OAuth2WebServerFlow(Flow): else: code = code['code'] - body = urllib.urlencode({ + body = urlencode({ 'grant_type': 'authorization_code', 'client_id': self.client_id, 'client_secret': self.client_secret, diff --git a/oauth2client/tools.py b/oauth2client/tools.py index dd3b128..5314aee 100644 --- a/oauth2client/tools.py +++ b/oauth2client/tools.py @@ -37,9 +37,12 @@ from oauth2client import file from oauth2client import util try: - from urlparse import parse_qsl + from urllib.parse import parse_qsl except ImportError: - from cgi import parse_qsl + try: + from urlparse import parse_qsl + except ImportError: + from cgi import parse_qsl _CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0 diff --git a/oauth2client/util.py b/oauth2client/util.py index 90dff15..b6ffd61 100644 --- a/oauth2client/util.py +++ b/oauth2client/util.py @@ -30,13 +30,15 @@ __all__ = [ import inspect import logging import types -import urllib -import urlparse - try: - from urlparse import parse_qsl + from urllib.parse import urlparse, urlunparse, urlencode, parse_qsl except ImportError: - from cgi import parse_qsl + from urlparse import urlparse, urlunparse + from urllib import urlencode + try: + from urlparse import parse_qsl + except ImportError: + from cgi import parse_qsl logger = logging.getLogger(__name__) @@ -189,8 +191,8 @@ def _add_query_parameter(url, name, value): if value is None: return url else: - parsed = list(urlparse.urlparse(url)) + parsed = list(urlparse(url)) q = dict(parse_qsl(parsed[4])) q[name] = value - parsed[4] = urllib.urlencode(q) - return urlparse.urlunparse(parsed) + parsed[4] = urlencode(q) + return urlunparse(parsed) From 07998dc53ff1e0d72c804ad580395db459d34cd5 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 09:29:44 -0700 Subject: [PATCH 08/69] Updating to Python3 - Adding 'long' alias if Python3 --- oauth2client/client.py | 2 ++ oauth2client/crypt.py | 4 ++++ oauth2client/service_account.py | 4 ++++ oauth2client/util.py | 4 ++++ oauth2client/xsrfutil.py | 4 ++++ 5 files changed, 18 insertions(+) diff --git a/oauth2client/client.py b/oauth2client/client.py index 01b111f..ec0f1d4 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -38,6 +38,8 @@ except ImportError: except ImportError: from cgi import parse_qsl +if sys.version > '3': + long = int from collections import namedtuple from oauth2client import GOOGLE_AUTH_URI diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 4d9f1c0..c64a079 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -20,6 +20,10 @@ import hashlib import logging import time +import sys +if sys.version > '3': + long = int + from oauth2client.anyjson import simplejson diff --git a/oauth2client/service_account.py b/oauth2client/service_account.py index de8c0a2..9596fea 100644 --- a/oauth2client/service_account.py +++ b/oauth2client/service_account.py @@ -22,6 +22,10 @@ import rsa import time import types +import sys +if sys.version > '3': + long = int + from oauth2client import GOOGLE_REVOKE_URI from oauth2client import GOOGLE_TOKEN_URI from oauth2client import util diff --git a/oauth2client/util.py b/oauth2client/util.py index b6ffd61..4a4e35a 100644 --- a/oauth2client/util.py +++ b/oauth2client/util.py @@ -40,6 +40,10 @@ except ImportError: except ImportError: from cgi import parse_qsl +import sys +if sys.version > '3': + long = int + logger = logging.getLogger(__name__) POSITIONAL_WARNING = 'WARNING' diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py index 7e1fe5c..f514a48 100644 --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -27,6 +27,10 @@ import hmac import os # for urandom import time +import sys +if sys.version > '3': + long = int + from oauth2client import util From 8c871bb113344ebd1914fcca35789f872dd17810 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 09:31:14 -0700 Subject: [PATCH 09/69] Updating to Python3 - Updating test for string type --- oauth2client/util.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/oauth2client/util.py b/oauth2client/util.py index 4a4e35a..8b1e19a 100644 --- a/oauth2client/util.py +++ b/oauth2client/util.py @@ -158,7 +158,11 @@ def scopes_to_string(scopes): Returns: The scopes formatted as a single string. """ - if isinstance(scopes, types.StringTypes): + try: + is_string = isinstance(scopes, basestring) + except NameError: + is_string = isinstance(scopes, str) + if is_string: return scopes else: return ' '.join(scopes) From 696078f77d04897618ab5eed9609751570b2d263 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 12:11:40 -0700 Subject: [PATCH 10/69] Updating to Python3 - Updating token functions to work with unicode --- oauth2client/xsrfutil.py | 27 +++++++++++++++------------ tests/test_xsrfutil.py | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py index f514a48..e5236bf 100644 --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -56,17 +56,20 @@ def generate_token(key, user_id, action_id="", when=None): A string XSRF protection token. """ when = when or int(time.time()) - digester = hmac.new(key) - digester.update(str(user_id)) - digester.update(DELIMITER) - digester.update(action_id) - digester.update(DELIMITER) - digester.update(str(when)) + decoded_key = '{key}{user_id}{delim}{action_id}{delim}{time}'.format(key=key, + user_id=user_id, + action_id=action_id, + delim=DELIMITER, + time=when).encode('utf-8') + + digester = hmac.new(decoded_key) digest = digester.digest() - token = base64.urlsafe_b64encode('%s%s%d' % (digest, - DELIMITER, - when)) + decoded = '{digest}{delim}{time}'.format(digest=digest, delim=DELIMITER, time=when) + + token = base64.urlsafe_b64encode('{digest}{delim}{time}'.format(digest=digest, + delim=DELIMITER, + time=when).encode('utf-8')) return token @@ -91,8 +94,8 @@ def validate_token(key, token, user_id, action_id="", current_time=None): if not token: return False try: - decoded = base64.urlsafe_b64decode(str(token)) - token_time = long(decoded.split(DELIMITER)[-1]) + decoded = base64.urlsafe_b64decode(token) + token_time = long(decoded.decode('utf-8').split(DELIMITER)[-1]) except (TypeError, ValueError): return False if current_time is None: @@ -110,7 +113,7 @@ def validate_token(key, token, user_id, action_id="", current_time=None): # Perform constant time comparison to avoid timing attacks different = 0 for x, y in zip(token, expected_token): - different |= ord(x) ^ ord(y) + different |= ord(chr(x)) ^ ord(chr(y)) if different: return False diff --git a/tests/test_xsrfutil.py b/tests/test_xsrfutil.py index a86a15b..409f77f 100644 --- a/tests/test_xsrfutil.py +++ b/tests/test_xsrfutil.py @@ -96,7 +96,7 @@ class XsrfUtilTests(unittest.TestCase): # Invalid with extra garbage self.assertFalse(xsrfutil.validate_token(TEST_KEY, - token + 'x', + token.decode('utf-8') + 'x', TEST_USER_ID_1, action_id=TEST_ACTION_ID_1, current_time=later15mins)) From f163904c987d12bab6cf781fc5133c0211d01d20 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 3 Jul 2014 12:29:49 -0700 Subject: [PATCH 11/69] Updating to Python3 - Updating token functions to work with unicode (Fixed some failures in Python2) --- oauth2client/xsrfutil.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py index e5236bf..843df02 100644 --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -37,6 +37,8 @@ from oauth2client import util # Delimiter character DELIMITER = ':' +ENCODING = 'utf-8' + # 1 hour in seconds DEFAULT_TIMEOUT_SECS = 1*60*60 @@ -60,16 +62,17 @@ def generate_token(key, user_id, action_id="", when=None): user_id=user_id, action_id=action_id, delim=DELIMITER, - time=when).encode('utf-8') + time=when).encode(ENCODING) digester = hmac.new(decoded_key) digest = digester.digest() - decoded = '{digest}{delim}{time}'.format(digest=digest, delim=DELIMITER, time=when) + decoded_token = '{digest}{delim}{time}'.format(digest=digest, delim=DELIMITER, time=when) - token = base64.urlsafe_b64encode('{digest}{delim}{time}'.format(digest=digest, - delim=DELIMITER, - time=when).encode('utf-8')) + try: + token = base64.urlsafe_b64encode(decoded_token.encode(ENCODING)) + except UnicodeDecodeError: + token = base64.urlsafe_b64encode(decoded_token) return token @@ -95,9 +98,17 @@ def validate_token(key, token, user_id, action_id="", current_time=None): return False try: decoded = base64.urlsafe_b64decode(token) - token_time = long(decoded.decode('utf-8').split(DELIMITER)[-1]) + # Decode is needed for Python3 + # It will fail for Python2 + token_time = long(decoded.decode(ENCODING).split(DELIMITER)[-1]) except (TypeError, ValueError): - return False + try: + # Try again, in case it fails here + decoded = base64.urlsafe_b64decode(token) + # Decode is not needed for Python2 + token_time = long(decoded.split(DELIMITER)[-1]) + except (TypeError, ValueError): + return False if current_time is None: current_time = time.time() # If the token is too old it's not valid. @@ -112,8 +123,14 @@ def validate_token(key, token, user_id, action_id="", current_time=None): # Perform constant time comparison to avoid timing attacks different = 0 - for x, y in zip(token, expected_token): - different |= ord(chr(x)) ^ ord(chr(y)) + try: + # Python3 + for x, y in zip(token, expected_token): + different |= ord(chr(x)) ^ ord(chr(y)) + except (TypeError, ValueError): + # Python2 + for x, y in zip(token.encode(ENCODING), expected_token.encode(ENCODING)): + different |= ord(x) ^ ord(y) if different: return False From bda35bd013cba6a43b51f3ee8d6a2a84ba1a5bd6 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Sat, 5 Jul 2014 21:51:34 -0700 Subject: [PATCH 12/69] Removing redundant function calls --- oauth2client/xsrfutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 oauth2client/xsrfutil.py diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py old mode 100644 new mode 100755 index 843df02..f33e73b --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -126,7 +126,7 @@ def validate_token(key, token, user_id, action_id="", current_time=None): try: # Python3 for x, y in zip(token, expected_token): - different |= ord(chr(x)) ^ ord(chr(y)) + different |= x ^ y except (TypeError, ValueError): # Python2 for x, y in zip(token.encode(ENCODING), expected_token.encode(ENCODING)): From 866ee3add0cfe67b16d896eef0dcbd729d2f9475 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Sun, 6 Jul 2014 01:04:13 -0700 Subject: [PATCH 13/69] Cleaned up more imports and used 'six' to handle iteritems() --- oauth2client/client.py | 25 +++++++++++++++++-------- oauth2client/clientsecrets.py | 5 ++++- 2 files changed, 21 insertions(+), 9 deletions(-) mode change 100644 => 100755 oauth2client/client.py mode change 100644 => 100755 oauth2client/clientsecrets.py diff --git a/oauth2client/client.py b/oauth2client/client.py old mode 100644 new mode 100755 index ec0f1d4..8eb59b1 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -20,7 +20,7 @@ Tools for interacting with OAuth 2.0 protected resources. __author__ = 'jcgregorio@google.com (Joe Gregorio)' import base64 -import clientsecrets +from oauth2client import clientsecrets import copy import datetime import httplib2 @@ -28,11 +28,15 @@ import logging import os import sys import time +import six try: from urllib.parse import urlparse, urlunparse, urlencode, parse_qsl + from urllib.request import urlopen + from urllib.error import URLError except ImportError: from urlparse import urlparse, urlunparse from urllib import urlencode + from urllib2 import urlopen, URLError try: from urlparse import parse_qsl except ImportError: @@ -378,7 +382,7 @@ def clean_headers(headers): """ clean = {} try: - for k, v in headers.iteritems(): + for k, v in six.iteritems(headers): clean[str(k)] = str(v) except UnicodeEncodeError: raise NonAsciiHeaderError(k + ': ' + v) @@ -892,16 +896,15 @@ def _get_environment(urllib2_urlopen=None): elif server_software.startswith('Development/'): _env_name = 'GAE_LOCAL' else: - import urllib2 try: if urllib2_urlopen is None: - urllib2_urlopen = urllib2.urlopen + urllib2_urlopen = urlopen response = urllib2_urlopen('http://metadata.google.internal') if any('Metadata-Flavor: Google' in h for h in response.info().headers): _env_name = 'GCE_PRODUCTION' else: _env_name = 'UNKNOWN' - except urllib2.URLError: + except URLError: _env_name = 'UNKNOWN' return _env_name @@ -1106,7 +1109,7 @@ def _get_well_known_file(): def _get_default_credential_from_file(default_credential_filename): """Build the Default Credentials from file.""" - import service_account + from oauth2client import service_account # read the credentials from the file with open(default_credential_filename) as default_credential: @@ -1609,7 +1612,13 @@ class OAuth2WebServerFlow(Flow): refresh_token. """ - if not isinstance(code, basestring): + try: + # Python2 + is_string = isinstance(code, basestring) + except NameError: + # Python3 + is_string = isinstance(code, str) + if not is_string: if 'code' not in code: if 'error' in code: error_msg = code['error'] @@ -1666,7 +1675,7 @@ class OAuth2WebServerFlow(Flow): logger.info('Failed to retrieve access token: %s' % content) if 'error' in d: # you never know what those providers got to say - error_msg = unicode(d['error']) + error_msg = str(d['error']) else: error_msg = 'Invalid response: %s.' % str(resp.status) raise FlowExchangeError(error_msg) diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py old mode 100644 new mode 100755 index 2c55dc0..4caedc2 --- a/oauth2client/clientsecrets.py +++ b/oauth2client/clientsecrets.py @@ -70,7 +70,10 @@ class InvalidClientSecretsError(Error): def _validate_clientsecrets(obj): if obj is None or len(obj) != 1: raise InvalidClientSecretsError('Invalid file format.') - client_type = obj.keys()[0] + try: + client_type = obj.keys()[0] + except TypeError: + client_type = list(obj.keys())[0] if client_type not in VALID_CLIENT.keys(): raise InvalidClientSecretsError('Unknown client type: %s.' % client_type) client_info = obj[client_type] From f25fd3cce7b7df450fd7245d406eac9b4feb4cfa Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Sun, 6 Jul 2014 01:07:03 -0700 Subject: [PATCH 14/69] Cleaned up more imports. Fixed exceptions. Used 'six' for iteritems(). --- tests/test_oauth2client.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) mode change 100644 => 100755 tests/test_oauth2client.py diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py old mode 100644 new mode 100755 index 72e3b4e..71ab6c3 --- a/tests/test_oauth2client.py +++ b/tests/test_oauth2client.py @@ -28,7 +28,11 @@ import mox import os import time import unittest -import urlparse +import six +try: + from urllib.parse import urlparse, parse_qs +except ImportError: + from urlparse import urlparse, parse_qs from http_mock import HttpMock from http_mock import HttpMockSequence @@ -77,15 +81,15 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') # googleapiclient.test_discovery; consolidate these definitions. def assertUrisEqual(testcase, expected, actual): """Test that URIs are the same, up to reordering of query parameters.""" - expected = urlparse.urlparse(expected) - actual = urlparse.urlparse(actual) + expected = urlparse(expected) + actual = urlparse(actual) testcase.assertEqual(expected.scheme, actual.scheme) testcase.assertEqual(expected.netloc, actual.netloc) testcase.assertEqual(expected.path, actual.path) testcase.assertEqual(expected.params, actual.params) testcase.assertEqual(expected.fragment, actual.fragment) - expected_query = urlparse.parse_qs(expected.query) - actual_query = urlparse.parse_qs(actual.query) + expected_query = parse_qs(expected.query) + actual_query = parse_qs(actual.query) for name in expected_query.keys(): testcase.assertEqual(expected_query[name], actual_query[name]) for name in actual_query.keys(): @@ -555,9 +559,9 @@ class BasicCredentialsTests(unittest.TestCase): client_id = u'some_client_id' client_secret = u'cOuDdkfjxxnv+' refresh_token = u'1/0/a.df219fjls0' - token_expiry = unicode(datetime.datetime.utcnow()) - token_uri = unicode(GOOGLE_TOKEN_URI) - revoke_uri = unicode(GOOGLE_REVOKE_URI) + token_expiry = str(datetime.datetime.utcnow()) + token_uri = str(GOOGLE_TOKEN_URI) + revoke_uri = str(GOOGLE_REVOKE_URI) user_agent = u'refresh_checker/1.0' credentials = OAuth2Credentials(access_token, client_id, client_secret, refresh_token, token_expiry, token_uri, @@ -566,7 +570,7 @@ class BasicCredentialsTests(unittest.TestCase): http = HttpMock(headers={'status': '200'}) http = credentials.authorize(http) http.request(u'http://example.com', method=u'GET', headers={u'foo': u'bar'}) - for k, v in http.headers.iteritems(): + for k, v in six.iteritems(http.headers): self.assertEqual(str, type(k)) self.assertEqual(str, type(v)) @@ -677,7 +681,7 @@ class TestAssertionCredentials(unittest.TestCase): user_agent=user_agent) def test_assertion_body(self): - body = urlparse.parse_qs(self.credentials._generate_refresh_request_body()) + body = parse_qs(self.credentials._generate_refresh_request_body()) self.assertEqual(self.assertion_text, body['assertion'][0]) self.assertEqual('urn:ietf:params:oauth:grant-type:jwt-bearer', body['grant_type'][0]) @@ -749,8 +753,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_construct_authorize_url(self): authorize_url = self.flow.step1_get_authorize_url() - parsed = urlparse.urlparse(authorize_url) - q = urlparse.parse_qs(parsed[4]) + parsed = urlparse(authorize_url) + q = parse_qs(parsed[4]) self.assertEqual('client_id+1', q['client_id'][0]) self.assertEqual('code', q['response_type'][0]) self.assertEqual('foo', q['scope'][0]) @@ -770,8 +774,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase): ) authorize_url = flow.step1_get_authorize_url() - parsed = urlparse.urlparse(authorize_url) - q = urlparse.parse_qs(parsed[4]) + parsed = urlparse(authorize_url) + q = parse_qs(parsed[4]) self.assertEqual('client_id+1', q['client_id'][0]) self.assertEqual('token', q['response_type'][0]) self.assertEqual('foo', q['scope'][0]) @@ -797,7 +801,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): try: credentials = self.flow.step2_exchange('some random code', http=http) self.fail('should raise exception if exchange doesn\'t get 200') - except FlowExchangeError, e: + except FlowExchangeError as e: self.assertEquals('invalid_request', str(e)) def test_exchange_failure_with_json_error(self): @@ -815,7 +819,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): try: credentials = self.flow.step2_exchange('some random code', http=http) self.fail('should raise exception if exchange doesn\'t get 200') - except FlowExchangeError, e: + except FlowExchangeError as e: pass def test_exchange_success(self): @@ -880,7 +884,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): try: credentials = self.flow.step2_exchange(code, http=http) self.fail('should raise exception if no code in dictionary.') - except FlowExchangeError, e: + except FlowExchangeError as e: self.assertTrue('shall not pass' in str(e)) def test_exchange_id_token_fail(self): From 428cc3d404dc4b01e0703a2b2606d23c49108ec1 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Mon, 14 Jul 2014 14:05:32 -0700 Subject: [PATCH 15/69] Updating Octal codes to new format (leading "0o" instead of leading "0") --- tests/test_file.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_file.py b/tests/test_file.py index 1de82af..a0ca8ee 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -41,6 +41,7 @@ from oauth2client.anyjson import simplejson from oauth2client.client import AccessTokenCredentials from oauth2client.client import AssertionCredentials from oauth2client.client import OAuth2Credentials +from future_builtins import oct FILENAME = tempfile.mktemp('oauth2client_test.data') @@ -155,13 +156,13 @@ class OAuth2ClientFileTests(unittest.TestCase): mode = os.stat(FILENAME).st_mode if os.name == 'posix': - self.assertEquals('0600', oct(stat.S_IMODE(os.stat(FILENAME).st_mode))) + self.assertEquals('0o600', oct(stat.S_IMODE(os.stat(FILENAME).st_mode))) def test_read_only_file_fail_lock(self): credentials = self.create_test_credentials() open(FILENAME, 'a+b').close() - os.chmod(FILENAME, 0400) + os.chmod(FILENAME, 0o400) store = multistore_file.get_credential_storage( FILENAME, @@ -172,7 +173,7 @@ class OAuth2ClientFileTests(unittest.TestCase): store.put(credentials) if os.name == 'posix': self.assertTrue(store._multistore._read_only) - os.chmod(FILENAME, 0600) + os.chmod(FILENAME, 0o600) def test_multistore_no_symbolic_link_files(self): if hasattr(os, 'symlink'): @@ -222,7 +223,7 @@ class OAuth2ClientFileTests(unittest.TestCase): self.assertEquals(None, credentials) if os.name == 'posix': - self.assertEquals('0600', oct(stat.S_IMODE(os.stat(FILENAME).st_mode))) + self.assertEquals('0o600', oct(stat.S_IMODE(os.stat(FILENAME).st_mode))) def test_multistore_file_custom_key(self): credentials = self.create_test_credentials() From 45ce2f3d0c06f412a0599613e72bb51c2c929906 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 09:42:41 -0700 Subject: [PATCH 16/69] Updated for Python3. Ensured unit tests for tests/test_clientsecrets.py pass in both python2 and 3. * StringIO.StringIO -> io.StringIO * assertEquals -> assertEqual * Exception Handling * dict.iteritems().next() -> next(iter(dict.items())) --- oauth2client/clientsecrets.py | 6 +++++- tests/test_clientsecrets.py | 31 ++++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py index 4caedc2..033b9bb 100755 --- a/oauth2client/clientsecrets.py +++ b/oauth2client/clientsecrets.py @@ -153,4 +153,8 @@ def loadfile(filename, cache=None): obj = {client_type: client_info} cache.set(filename, obj, namespace=_SECRET_NAMESPACE) - return obj.iteritems().next() + try: + items = obj.iteritems().next() + except AttributeError: + items = next(iter(obj.items())) + return items diff --git a/tests/test_clientsecrets.py b/tests/test_clientsecrets.py index f69fb36..e407d87 100644 --- a/tests/test_clientsecrets.py +++ b/tests/test_clientsecrets.py @@ -19,7 +19,10 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import os import unittest -import StringIO +try: + from io import StringIO +except ImportError: + from StringIO import StringIO import httplib2 @@ -57,26 +60,28 @@ class OAuth2CredentialsTests(unittest.TestCase): """, 'Property'), ] for src, match in ERRORS: + # Ensure that it is unicode + src = u'%s'%src # Test load(s) try: clientsecrets.loads(src) self.fail(src + ' should not be a valid client_secrets file.') - except clientsecrets.InvalidClientSecretsError, e: + except clientsecrets.InvalidClientSecretsError as e: self.assertTrue(str(e).startswith(match)) # Test loads(fp) try: - fp = StringIO.StringIO(src) + fp = StringIO(src) clientsecrets.load(fp) self.fail(src + ' should not be a valid client_secrets file.') - except clientsecrets.InvalidClientSecretsError, e: + except clientsecrets.InvalidClientSecretsError as e: self.assertTrue(str(e).startswith(match)) def test_load_by_filename(self): try: clientsecrets._loadfile(NONEXISTENT_FILE) self.fail('should fail to load a missing client_secrets file.') - except clientsecrets.InvalidClientSecretsError, e: + except clientsecrets.InvalidClientSecretsError as e: self.assertTrue(str(e).startswith('File')) @@ -104,25 +109,25 @@ class CachedClientsecretsTests(unittest.TestCase): def test_cache_miss(self): client_type, client_info = clientsecrets.loadfile( VALID_FILE, cache=self.cache_mock) - self.assertEquals('web', client_type) - self.assertEquals('foo_client_secret', client_info['client_secret']) + self.assertEqual('web', client_type) + self.assertEqual('foo_client_secret', client_info['client_secret']) cached = self.cache_mock.cache[VALID_FILE] - self.assertEquals({client_type: client_info}, cached) + self.assertEqual({client_type: client_info}, cached) # make sure we're using non-empty namespace ns = self.cache_mock.last_set_ns self.assertTrue(bool(ns)) # make sure they're equal - self.assertEquals(ns, self.cache_mock.last_get_ns) + self.assertEqual(ns, self.cache_mock.last_get_ns) def test_cache_hit(self): self.cache_mock.cache[NONEXISTENT_FILE] = { 'web': 'secret info' } client_type, client_info = clientsecrets.loadfile( NONEXISTENT_FILE, cache=self.cache_mock) - self.assertEquals('web', client_type) - self.assertEquals('secret info', client_info) + self.assertEqual('web', client_type) + self.assertEqual('secret info', client_info) # make sure we didn't do any set() RPCs self.assertEqual(None, self.cache_mock.last_set_ns) @@ -137,8 +142,8 @@ class CachedClientsecretsTests(unittest.TestCase): def test_without_cache(self): # this also ensures loadfile() is backward compatible client_type, client_info = clientsecrets.loadfile(VALID_FILE) - self.assertEquals('web', client_type) - self.assertEquals('foo_client_secret', client_info['client_secret']) + self.assertEqual('web', client_type) + self.assertEqual('foo_client_secret', client_info['client_secret']) if __name__ == '__main__': From b04ef0a72fe320af06bc521a68eeed9a8b16b6ad Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 09:57:07 -0700 Subject: [PATCH 17/69] Fixed conversion to unicode (for failure in 3.2) --- tests/test_clientsecrets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_clientsecrets.py b/tests/test_clientsecrets.py index e407d87..b21ccad 100644 --- a/tests/test_clientsecrets.py +++ b/tests/test_clientsecrets.py @@ -61,7 +61,10 @@ class OAuth2CredentialsTests(unittest.TestCase): ] for src, match in ERRORS: # Ensure that it is unicode - src = u'%s'%src + try: + src = src.decode('utf-8') + except AttributeError: + pass # Test load(s) try: clientsecrets.loads(src) From e80fb032d61c3eae9ef2933b9d24f34b0c065785 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 10:44:39 -0700 Subject: [PATCH 18/69] Updating urllib imports and usage --- uritemplate/__init__.py | 43 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/uritemplate/__init__.py b/uritemplate/__init__.py index 5d0ebce..f2b5f1e 100644 --- a/uritemplate/__init__.py +++ b/uritemplate/__init__.py @@ -1,7 +1,10 @@ # Early, and incomplete implementation of -04. # import re -import urllib +try: + from urllib.parse import quote +except ImportError: + from urllib import quote RESERVED = ":/?#[]@!$&'()*+,;=" OPERATOR = "+./;?|!@" @@ -13,41 +16,41 @@ VAR = re.compile(r"^(?P[^=\+\*:\^]+)((?P[\+\*])|(?P[: def _tostring(varname, value, explode, operator, safe=""): if type(value) == type([]): if explode == "+": - return ",".join([varname + "." + urllib.quote(x, safe) for x in value]) + return ",".join([varname + "." + quote(x, safe) for x in value]) else: - return ",".join([urllib.quote(x, safe) for x in value]) + return ",".join([quote(x, safe) for x in value]) if type(value) == type({}): keys = value.keys() keys.sort() if explode == "+": - return ",".join([varname + "." + urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) + return ",".join([varname + "." + quote(key, safe) + "," + quote(value[key], safe) for key in keys]) else: - return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) + return ",".join([quote(key, safe) + "," + quote(value[key], safe) for key in keys]) else: - return urllib.quote(value, safe) + return quote(value, safe) def _tostring_path(varname, value, explode, operator, safe=""): joiner = operator if type(value) == type([]): if explode == "+": - return joiner.join([varname + "." + urllib.quote(x, safe) for x in value]) + return joiner.join([varname + "." + quote(x, safe) for x in value]) elif explode == "*": - return joiner.join([urllib.quote(x, safe) for x in value]) + return joiner.join([quote(x, safe) for x in value]) else: - return ",".join([urllib.quote(x, safe) for x in value]) + return ",".join([quote(x, safe) for x in value]) elif type(value) == type({}): keys = value.keys() keys.sort() if explode == "+": - return joiner.join([varname + "." + urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) + return joiner.join([varname + "." + quote(key, safe) + joiner + quote(value[key], safe) for key in keys]) elif explode == "*": - return joiner.join([urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) + return joiner.join([quote(key, safe) + joiner + quote(value[key], safe) for key in keys]) else: - return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) + return ",".join([quote(key, safe) + "," + quote(value[key], safe) for key in keys]) else: if value: - return urllib.quote(value, safe) + return quote(value, safe) else: return "" @@ -61,25 +64,25 @@ def _tostring_query(varname, value, explode, operator, safe=""): if 0 == len(value): return "" if explode == "+": - return joiner.join([varname + "=" + urllib.quote(x, safe) for x in value]) + return joiner.join([varname + "=" + quote(x, safe) for x in value]) elif explode == "*": - return joiner.join([urllib.quote(x, safe) for x in value]) + return joiner.join([quote(x, safe) for x in value]) else: - return varprefix + ",".join([urllib.quote(x, safe) for x in value]) + return varprefix + ",".join([quote(x, safe) for x in value]) elif type(value) == type({}): if 0 == len(value): return "" keys = value.keys() keys.sort() if explode == "+": - return joiner.join([varname + "." + urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) + return joiner.join([varname + "." + quote(key, safe) + "=" + quote(value[key], safe) for key in keys]) elif explode == "*": - return joiner.join([urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) + return joiner.join([quote(key, safe) + "=" + quote(value[key], safe) for key in keys]) else: - return varprefix + ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) + return varprefix + ",".join([quote(key, safe) + "," + quote(value[key], safe) for key in keys]) else: if value: - return varname + "=" + urllib.quote(value, safe) + return varname + "=" + quote(value, safe) else: return varname From ff96d015817a920fe76ff5867e317e5f55095033 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 10:53:57 -0700 Subject: [PATCH 19/69] Adding additional CI environments --- .travis.yml | 8 +++++++- tox.ini | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39df0ef..9c7a5ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,14 @@ language: python -python: 2.7 +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" env: - TOX_ENV=py26 - TOX_ENV=py27 + - TOX_ENV=py32 + - TOX_ENV=py33 - TOX_ENV=pypy install: - pip install tox diff --git a/tox.ini b/tox.ini index 33f3af8..f7a382b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] -envlist = py26, py27 +envlist = py26, py27, py32, py33 [testenv] deps = keyring - mox + mox3 pyopenssl pycrypto==2.6 django==1.2 From 32d6aacac7a5965b79475bd21fd8c5fa86e8139e Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 11:03:01 -0700 Subject: [PATCH 20/69] Updating mox import to mox3 --- tests/test_gce.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_gce.py b/tests/test_gce.py index 60e876e..8ec59be 100644 --- a/tests/test_gce.py +++ b/tests/test_gce.py @@ -21,7 +21,10 @@ Unit tests for oauth2client.gce. __author__ = 'jcgregorio@google.com (Joe Gregorio)' import httplib2 -import mox +try: + from mox3 import mox +except ImportError: + import mox import unittest from oauth2client.client import AccessTokenRefreshError From dd1d8ff11ea37534a85abf5349013fe166aabc4c Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 11:11:43 -0700 Subject: [PATCH 21/69] Adjusting CI environments (2.6 and 3.2 only) --- .travis.yml | 4 ---- tox.ini | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c7a5ae..62d7640 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,10 @@ language: python python: - "2.6" - - "2.7" - "3.2" - - "3.3" env: - TOX_ENV=py26 - - TOX_ENV=py27 - TOX_ENV=py32 - - TOX_ENV=py33 - TOX_ENV=pypy install: - pip install tox diff --git a/tox.ini b/tox.ini index f7a382b..6654296 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist = py26, py27, py32, py33 [testenv] deps = keyring + mox mox3 pyopenssl pycrypto==2.6 @@ -11,3 +12,9 @@ deps = keyring nose setenv = PYTHONPATH=../google_appengine commands = nosetests --ignore-files=test_appengine\.py + +# whitelist +branches: + only: + - master + - python3 \ No newline at end of file From ec9cdfc8958b5418d4d99b888082c03dd188e0df Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 11:31:31 -0700 Subject: [PATCH 22/69] Some more adjustments to the CI configuration. (Matching python versions to corresponding environments) --- .travis.yml | 21 +++++++++++++-------- tox.ini | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62d7640..a7ba510 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,20 @@ language: python -python: - - "2.6" - - "3.2" -env: - - TOX_ENV=py26 - - TOX_ENV=py32 - - TOX_ENV=pypy +matrix: + include: + - python: "2.6" + env: TOX_ENV=py26 + - python: "2.7" + env: TOX_ENV=py27 + - python: "3.2" + env: TOX_ENV=py32 + - python: "3.3" + env: TOX_ENV=py33 + - python: "pypy" + env: TOX_ENV=pypy install: - pip install tox - pip install . script: - tox -e $TOX_ENV notifications: - - email: false + email: false diff --git a/tox.ini b/tox.ini index 6654296..45ef117 100644 --- a/tox.ini +++ b/tox.ini @@ -17,4 +17,4 @@ commands = nosetests --ignore-files=test_appengine\.py branches: only: - master - - python3 \ No newline at end of file + - python3 From 5c266e20101e7263a3e4436dbd1e10fd0eb379c7 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 13:05:01 -0700 Subject: [PATCH 23/69] Updated for Python3. Ensured unit tests for tests/test_service_account.py pass in both python2 and 3. * Lots of little hiccups with str vs bytes * Explicit relative import * More fun with str vs bytes (There may be a better way to handle them, but this works for now) --- oauth2client/service_account.py | 12 +++++++++--- tests/test_service_account.py | 10 +++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/oauth2client/service_account.py b/oauth2client/service_account.py index 9596fea..ee82aae 100644 --- a/oauth2client/service_account.py +++ b/oauth2client/service_account.py @@ -81,14 +81,20 @@ class _ServiceAccountCredentials(AssertionCredentials): assertion_input = '%s.%s' % ( _urlsafe_b64encode(header), _urlsafe_b64encode(payload)) + assertion_input = assertion_input.encode('utf-8') # Sign the assertion. - signature = base64.urlsafe_b64encode(rsa.pkcs1.sign( - assertion_input, self._private_key, 'SHA-256')).rstrip('=') + signature = bytes.decode(base64.urlsafe_b64encode(rsa.pkcs1.sign( + assertion_input, self._private_key, 'SHA-256'))).rstrip('=') return '%s.%s' % (assertion_input, signature) def sign_blob(self, blob): + # Ensure that it is bytes + try: + blob = blob.encode('utf-8') + except AttributeError: + pass return (self._private_key_id, rsa.pkcs1.sign(blob, self._private_key, 'SHA-256')) @@ -113,7 +119,7 @@ class _ServiceAccountCredentials(AssertionCredentials): def _urlsafe_b64encode(data): return base64.urlsafe_b64encode( simplejson.dumps(data, separators = (',', ':'))\ - .encode('UTF-8')).rstrip('=') + .rstrip('=').encode('UTF-8')) def _get_private_key(private_key_pkcs8_text): """Get an RSA private key object from a pkcs8 representation.""" diff --git a/tests/test_service_account.py b/tests/test_service_account.py index 56313d3..41e481a 100644 --- a/tests/test_service_account.py +++ b/tests/test_service_account.py @@ -25,14 +25,14 @@ import rsa import time import unittest -from http_mock import HttpMockSequence +from .http_mock import HttpMockSequence from oauth2client.anyjson import simplejson from oauth2client.service_account import _ServiceAccountCredentials def datafile(filename): # TODO(orestica): Refactor this using pkgutil.get_data - f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'r') + f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'rb') data = f.read() f.close() return data @@ -58,16 +58,16 @@ class ServiceAccountCredentialsTests(unittest.TestCase): pub_key = rsa.PublicKey.load_pkcs1_openssl_pem( datafile('publickey_openssl.pem')) - self.assertTrue(rsa.pkcs1.verify('Google', signature, pub_key)) + self.assertTrue(rsa.pkcs1.verify(b'Google', signature, pub_key)) try: - rsa.pkcs1.verify('Orest', signature, pub_key) + rsa.pkcs1.verify(b'Orest', signature, pub_key) self.fail('Verification should have failed!') except rsa.pkcs1.VerificationError: pass # Expected try: - rsa.pkcs1.verify('Google', 'bad signature', pub_key) + rsa.pkcs1.verify(b'Google', b'bad signature', pub_key) self.fail('Verification should have failed!') except rsa.pkcs1.VerificationError: pass # Expected From 52d30620985e2ef01e373047fd778483f831a9d1 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 13:43:19 -0700 Subject: [PATCH 24/69] Adding keyring (>= 3.0) to the requirements list. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 4e738fa..ecdda94 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ install_requires = [ 'pyasn1==0.1.7', 'pyasn1_modules==0.0.5', 'rsa==3.1.4', + 'keyring>=3.0', # Not completely sure the minimum version, but 3.0 (Sept 2013) should be sufficient ] needs_json = False From 056ab9a6c3ec08ecfe3c90bc0ee87b8ce5bea5be Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 13:43:44 -0700 Subject: [PATCH 25/69] Adding mox3 import --- tests/test_keyring.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_keyring.py b/tests/test_keyring.py index e5b9971..e032625 100644 --- a/tests/test_keyring.py +++ b/tests/test_keyring.py @@ -23,7 +23,10 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import datetime import keyring import unittest -import mox +try: + from mox3 import mox +except ImportError: + import mox from oauth2client import GOOGLE_TOKEN_URI from oauth2client.client import OAuth2Credentials From 718399bb7d4301f410b0792f32980b0363078e6a Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Wed, 16 Jul 2014 14:21:37 -0700 Subject: [PATCH 26/69] Explicit relative imports, trying to use future_builtins.oct() for python2.x. --- tests/test_file.py | 10 +++++++--- tests/test_oauth2client.py | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_file.py b/tests/test_file.py index a0ca8ee..83989ca 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -31,7 +31,7 @@ import stat import tempfile import unittest -from http_mock import HttpMockSequence +from .http_mock import HttpMockSequence from oauth2client import GOOGLE_TOKEN_URI from oauth2client import file from oauth2client import locked_file @@ -41,7 +41,11 @@ from oauth2client.anyjson import simplejson from oauth2client.client import AccessTokenCredentials from oauth2client.client import AssertionCredentials from oauth2client.client import OAuth2Credentials -from future_builtins import oct +try: + # Python2 + from future_builtins import oct +except: + pass FILENAME = tempfile.mktemp('oauth2client_test.data') @@ -97,7 +101,7 @@ class OAuth2ClientFileTests(unittest.TestCase): # Write a file with a pickled OAuth2Credentials. credentials = self.create_test_credentials() - f = open(FILENAME, 'w') + f = open(FILENAME, 'wb') pickle.dump(credentials, f) f.close() diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py index 71ab6c3..2a3399a 100755 --- a/tests/test_oauth2client.py +++ b/tests/test_oauth2client.py @@ -34,8 +34,8 @@ try: except ImportError: from urlparse import urlparse, parse_qs -from http_mock import HttpMock -from http_mock import HttpMockSequence +from .http_mock import HttpMock +from .http_mock import HttpMockSequence from oauth2client import GOOGLE_REVOKE_URI from oauth2client import GOOGLE_TOKEN_URI from oauth2client.anyjson import simplejson From 7ac4444d7f9ed7bad7dd67ebee233942fe0c8697 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Wed, 16 Jul 2014 14:22:44 -0700 Subject: [PATCH 27/69] Added handling for bytes vs str. --- oauth2client/client.py | 21 ++++++++++++++++++--- oauth2client/crypt.py | 15 ++++++++++----- oauth2client/file.py | 2 +- oauth2client/multistore_file.py | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 8eb59b1..baa93e1 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -238,7 +238,12 @@ class Credentials(object): An instance of the subclass of Credentials that was serialized with to_json(). """ - data = simplejson.loads(s) + try: + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) + except TypeError: + # s: already str + data = simplejson.loads(s) # Find and call the right classmethod from_json() to restore the object. module = data['_module'] try: @@ -573,7 +578,12 @@ class OAuth2Credentials(Credentials): Returns: An instance of a Credentials subclass. """ - data = simplejson.loads(s) + try: + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) + except TypeError: + # s: already str + data = simplejson.loads(s) if 'token_expiry' in data and not isinstance(data['token_expiry'], datetime.datetime): try: @@ -859,7 +869,12 @@ class AccessTokenCredentials(OAuth2Credentials): @classmethod def from_json(cls, s): - data = simplejson.loads(s) + try: + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) + except TypeError: + # s: already str + data = simplejson.loads(s) retval = AccessTokenCredentials( data['access_token'], data['user_agent']) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index c64a079..afcf81d 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -112,7 +112,7 @@ try: Returns: string, The signature of the message for the given key. """ - return crypto.sign(self._key, message, 'sha256') + return crypto.sign(self._key, str.encode(message), 'sha256') @staticmethod def from_string(key, password='notasecret'): @@ -273,21 +273,26 @@ def _parse_pem_key(raw_key_input): Returns: string, The actual key if the contents are from a PEM file, or else None. """ - offset = raw_key_input.find('-----BEGIN ') + offset = raw_key_input.find(b'-----BEGIN ') if offset != -1: return raw_key_input[offset:] else: return None def _urlsafe_b64encode(raw_bytes): - return base64.urlsafe_b64encode(raw_bytes).rstrip('=') + # Make sure our bytes are actually bytes + try: + raw_bytes = str.encode(raw_bytes) + except TypeError: + pass + return bytes.decode(base64.urlsafe_b64encode(raw_bytes)).rstrip('=') def _urlsafe_b64decode(b64string): # Guard against unicode strings, which base64 can't handle. b64string = b64string.encode('ascii') - padded = b64string + '=' * (4 - len(b64string) % 4) - return base64.urlsafe_b64decode(padded) + padded = b64string + b'=' * (4 - len(b64string) % 4) + return bytes.decode(base64.urlsafe_b64decode(padded)) def _json_encode(data): diff --git a/oauth2client/file.py b/oauth2client/file.py index 7c84b2f..89d2ee7 100644 --- a/oauth2client/file.py +++ b/oauth2client/file.py @@ -110,7 +110,7 @@ class Storage(BaseStorage): self._create_file_if_needed() self._validate_file() - f = open(self._filename, 'wb') + f = open(self._filename, 'w') f.write(credentials.to_json()) f.close() diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py index ba3d530..aa18e65 100644 --- a/oauth2client/multistore_file.py +++ b/oauth2client/multistore_file.py @@ -193,7 +193,7 @@ class _MultiStore(object): This will create the file if necessary. """ - self._file = LockedFile(filename, 'r+b', 'rb') + self._file = LockedFile(filename, 'r+', 'r') self._thread_lock = threading.Lock() self._read_only = False self._warn_on_readonly = warn_on_readonly From 9eec4a513d96526f6c4b73e65b9c65deebd29de8 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Wed, 16 Jul 2014 15:00:57 -0700 Subject: [PATCH 28/69] Assume it is a str first, then convert to str if it fails. --- oauth2client/client.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index baa93e1..74f3c2a 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -239,11 +239,11 @@ class Credentials(object): to_json(). """ try: - # s: bytes -> str - data = simplejson.loads(bytes.decode(s)) - except TypeError: # s: already str data = simplejson.loads(s) + except TypeError: + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) # Find and call the right classmethod from_json() to restore the object. module = data['_module'] try: @@ -579,11 +579,11 @@ class OAuth2Credentials(Credentials): An instance of a Credentials subclass. """ try: - # s: bytes -> str - data = simplejson.loads(bytes.decode(s)) - except TypeError: # s: already str data = simplejson.loads(s) + except TypeError: + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) if 'token_expiry' in data and not isinstance(data['token_expiry'], datetime.datetime): try: @@ -870,11 +870,11 @@ class AccessTokenCredentials(OAuth2Credentials): @classmethod def from_json(cls, s): try: - # s: bytes -> str - data = simplejson.loads(bytes.decode(s)) - except TypeError: # s: already str data = simplejson.loads(s) + except TypeError: + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) retval = AccessTokenCredentials( data['access_token'], data['user_agent']) From 82ca356f5672526903380faedd7dddf13f38b9ce Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Wed, 16 Jul 2014 15:02:13 -0700 Subject: [PATCH 29/69] Ensure message is in bytes --- oauth2client/crypt.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index afcf81d..54980a4 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -112,7 +112,11 @@ try: Returns: string, The signature of the message for the given key. """ - return crypto.sign(self._key, str.encode(message), 'sha256') + try: + message = str.encode(message) + except TypeError: + pass + return crypto.sign(self._key, message, 'sha256') @staticmethod def from_string(key, password='notasecret'): @@ -283,7 +287,7 @@ def _urlsafe_b64encode(raw_bytes): # Make sure our bytes are actually bytes try: raw_bytes = str.encode(raw_bytes) - except TypeError: + except (TypeError, UnicodeDecodeError): pass return bytes.decode(base64.urlsafe_b64encode(raw_bytes)).rstrip('=') @@ -292,7 +296,7 @@ def _urlsafe_b64decode(b64string): # Guard against unicode strings, which base64 can't handle. b64string = b64string.encode('ascii') padded = b64string + b'=' * (4 - len(b64string) % 4) - return bytes.decode(base64.urlsafe_b64decode(padded)) + return base64.urlsafe_b64decode(padded) def _json_encode(data): From d5a10a52b6d010a9dbfdc5847a4f7df2cc0902bc Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Wed, 16 Jul 2014 15:58:32 -0700 Subject: [PATCH 30/69] Updated library imports. bytes vs str handling. --- tests/test_jwt.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 8b17fea..7704397 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -28,14 +28,16 @@ import sys import tempfile import time import unittest -import urlparse try: - from urlparse import parse_qs + from urllib.parse import parse_qs except ImportError: + try: + from urlparse import parse_qs + except ImportError: from cgi import parse_qs -from http_mock import HttpMockSequence +from .http_mock import HttpMockSequence from oauth2client import crypt from oauth2client.anyjson import simplejson from oauth2client.client import Credentials @@ -46,9 +48,11 @@ from oauth2client.client import HAS_OPENSSL from oauth2client.client import HAS_CRYPTO from oauth2client.file import Storage +if sys.version > '3': + long = int def datafile(filename): - f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'r') + f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'rb') data = f.read() f.close() return data @@ -76,11 +80,10 @@ class CryptTests(unittest.TestCase): signature = signer.sign('foo') verifier = self.verifier.from_string(public_key, True) + self.assertTrue(verifier.verify(b'foo', signature)) - self.assertTrue(verifier.verify('foo', signature)) - - self.assertFalse(verifier.verify('bar', signature)) - self.assertFalse(verifier.verify('foo', 'bad signagure')) + self.assertFalse(verifier.verify(b'bar', signature)) + self.assertFalse(verifier.verify(b'foo', 'bad signagure')) def _check_jwt_failure(self, jwt, expected_error): try: From 60482ed7d8ad3eb4d4e3b38c00c34a4c0f9e9a52 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Wed, 16 Jul 2014 15:59:02 -0700 Subject: [PATCH 31/69] More bytes vs str handling. --- oauth2client/client.py | 11 +++++++++++ oauth2client/crypt.py | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/oauth2client/client.py b/oauth2client/client.py index 74f3c2a..f119673 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1307,6 +1307,11 @@ if HAS_CRYPTO: # Keep base64 encoded so it can be stored in JSON. self.private_key = base64.b64encode(private_key) + try: + # Ensure it's a str + self.private_key = bytes.decode(self.private_key) + except TypeError: + pass self.private_key_password = private_key_password self.service_account_name = service_account_name @@ -1314,6 +1319,11 @@ if HAS_CRYPTO: @classmethod def from_json(cls, s): + try: + # Ensure it's a str + s = bytes.decode(s) + except TypeError: + pass data = simplejson.loads(s) retval = SignedJwtAssertionCredentials( data['service_account_name'], @@ -1377,6 +1387,7 @@ if HAS_CRYPTO: resp, content = http.request(cert_uri) if resp.status == 200: + content = bytes.decode(content) certs = simplejson.loads(content) return crypt.verify_signed_jwt_with_certs(id_token, certs, audience) else: diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 54980a4..4dd4288 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -222,6 +222,10 @@ try: Returns: string, The signature of the message for the given key. """ + try: + message = str.encode(message) + except TypeError: + pass return PKCS1_v1_5.new(self._key).sign(SHA256.new(message)) @staticmethod @@ -354,12 +358,17 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): raise AppIdentityError( 'Wrong number of segments in token: %s' % jwt) signed = '%s.%s' % (segments[0], segments[1]) + try: + signed = str.encode(signed) + except TypeError: + pass signature = _urlsafe_b64decode(segments[2]) # Parse token. json_body = _urlsafe_b64decode(segments[1]) try: + json_body = bytes.decode(json_body) parsed = simplejson.loads(json_body) except: raise AppIdentityError('Can\'t parse token: %s' % json_body) From 270ec045bbb93a3a8eadf9d0759c4b5b6dedc8e4 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 08:56:32 -0700 Subject: [PATCH 32/69] More bytes vs str handling. Ensure private key is in bytes. Ensure dict components are str before creating JSON string. --- oauth2client/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index f119673..fb20828 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -215,6 +215,9 @@ class Credentials(object): # Add in information we will need later to reconsistitue this instance. d['_class'] = t.__name__ d['_module'] = t.__module__ + for key in d.keys(): + if isinstance(d[key], bytes): + d[key] = bytes.decode(d[key]) return simplejson.dumps(d) def to_json(self): @@ -1308,8 +1311,8 @@ if HAS_CRYPTO: # Keep base64 encoded so it can be stored in JSON. self.private_key = base64.b64encode(private_key) try: - # Ensure it's a str - self.private_key = bytes.decode(self.private_key) + # Ensure it's bytes + self.private_key = str.encode(self.private_key) except TypeError: pass From b32858b6301d9b5bd53d43e887ea3e53e43a34b8 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 13:10:07 -0700 Subject: [PATCH 33/69] Ensure password is str --- oauth2client/crypt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 4dd4288..1082c40 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -136,6 +136,9 @@ try: if parsed_pem_key: pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key) else: + # Ensure password is str + if isinstance(password, bytes): + password = bytes.decode(password) pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) From c16eba364d5602ab3e07eabd9ed5d95ec8546b7c Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 13:19:26 -0700 Subject: [PATCH 34/69] Setting up multiple tox test environments --- .travis.yml | 23 +++++++++++------------ tox.ini | 45 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7ba510..d674f8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,15 @@ language: python -matrix: - include: - - python: "2.6" - env: TOX_ENV=py26 - - python: "2.7" - env: TOX_ENV=py27 - - python: "3.2" - env: TOX_ENV=py32 - - python: "3.3" - env: TOX_ENV=py33 - - python: "pypy" - env: TOX_ENV=pypy +python: 2.7 +env: + - TOX_ENV=py26openssl13 + - TOX_ENV=py26openssl14 + - TOX_ENV=py27openssl13 + - TOX_ENV=py27openssl14 + - TOX_ENV=py32openssl13 + - TOX_ENV=py32openssl14 + - TOX_ENV=py33openssl13 + - TOX_ENV=py33openssl14 + - TOX_ENV=pypy install: - pip install tox - pip install . diff --git a/tox.ini b/tox.ini index 45ef117..2e047f4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,10 @@ [tox] -envlist = py26, py27, py32, py33 +#envlist = py26, py27, py32, py33 +envlist = py26openssl13, py26openssl14, py27openssl13, py27openssl14, py32openssl13, py32openssl14, py33openssl13, py33openssl14 [testenv] deps = keyring - mox mox3 - pyopenssl pycrypto==2.6 django==1.2 webtest @@ -18,3 +17,43 @@ branches: only: - master - python3 + +[testenv:py26openssl13] +basepython = python2.6 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py26openssl14] +basepython = python2.6 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py27openssl13] +basepython = python2.7 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py27openssl14] +basepython = python2.7 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py32openssl13] +basepython = python3.2 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py32openssl14] +basepython = python3.2 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py33openssl13] +basepython = python3.3 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py33openssl14] +basepython = python3.3 +deps = {[testenv]deps} + pyopenssl==0.14 From ca96e1a4245812825c671ccc010b8245a831a2fa Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 15:19:31 -0700 Subject: [PATCH 35/69] Updating to Django 1.5 for Python3 support --- tests/test_django_orm.py | 4 +++- tox.ini | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_django_orm.py b/tests/test_django_orm.py index c80ff1d..6580982 100644 --- a/tests/test_django_orm.py +++ b/tests/test_django_orm.py @@ -32,7 +32,7 @@ import unittest # Ensure that if app engine is available, we use the correct django from it try: from google.appengine.dist import use_library - use_library('django', '1.2') + use_library('django', '1.5') except ImportError: pass @@ -40,6 +40,8 @@ from oauth2client.client import Credentials from oauth2client.client import Flow # Mock a Django environment +from django.conf import global_settings +global_settings.SECRET_KEY = 'NotASecret' os.environ['DJANGO_SETTINGS_MODULE'] = 'django_settings' sys.modules['django_settings'] = imp.new_module('django_settings') diff --git a/tox.ini b/tox.ini index 2e047f4..9f3dbf4 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = py26openssl13, py26openssl14, py27openssl13, py27openssl14, py32openss deps = keyring mox3 pycrypto==2.6 - django==1.2 + django==1.5 webtest nose setenv = PYTHONPATH=../google_appengine From 466e3737d7b7d9e48cf983872221ebb90eddbdcf Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 15:35:55 -0700 Subject: [PATCH 36/69] Trying to handle both OpenSSL 0.13 and 0.14. Major change in load_pkcs12: 0.13: Need str 0.14: Need bytes --- oauth2client/crypt.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 1082c40..4d92545 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -136,10 +136,17 @@ try: if parsed_pem_key: pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key) else: + # OpenSSL 0.13 needs password to be str + # OpenSSL 0.14 needs password to be bytes # Ensure password is str if isinstance(password, bytes): password = bytes.decode(password) - pkey = crypto.load_pkcs12(key, password).get_privatekey() + try: + pkey = crypto.load_pkcs12(key, password).get_privatekey() + except TypeError: + # Failed as str, so let's try with bytes (probably 0.14+) + password = str.encode(password) + pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) except ImportError: From e42c11da83050624a08a98fd7575c6cb5206e153 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 21:05:16 -0700 Subject: [PATCH 37/69] Handle str and bytes better in crypto (loading and signing) --- oauth2client/crypt.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) mode change 100644 => 100755 oauth2client/crypt.py diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py old mode 100644 new mode 100755 index 4d92545..0f5cdc0 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -112,11 +112,14 @@ try: Returns: string, The signature of the message for the given key. """ + message = str(message) try: - message = str.encode(message) + signed_message = crypto.sign(self._key, message, 'sha256') except TypeError: - pass - return crypto.sign(self._key, message, 'sha256') + # Failed as str, so let's try with bytes (probably 0.14+) + message = bytes.decode(message) + signed_message = crypto.sign(self._key, message, 'sha256') + return signed_message @staticmethod def from_string(key, password='notasecret'): @@ -138,14 +141,12 @@ try: else: # OpenSSL 0.13 needs password to be str # OpenSSL 0.14 needs password to be bytes - # Ensure password is str - if isinstance(password, bytes): - password = bytes.decode(password) + password = str(password) try: pkey = crypto.load_pkcs12(key, password).get_privatekey() except TypeError: # Failed as str, so let's try with bytes (probably 0.14+) - password = str.encode(password) + password = bytes.decode(password) pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) @@ -367,11 +368,7 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): if (len(segments) != 3): raise AppIdentityError( 'Wrong number of segments in token: %s' % jwt) - signed = '%s.%s' % (segments[0], segments[1]) - try: - signed = str.encode(signed) - except TypeError: - pass + signed = str('%s.%s' % (segments[0], segments[1])) signature = _urlsafe_b64decode(segments[2]) From 503bc2ec6f650a2e3a7b0aa03bf050024011c137 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 21:12:41 -0700 Subject: [PATCH 38/69] Fixed str -> bytes conversion --- oauth2client/crypt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 0f5cdc0..9ba8099 100755 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -117,7 +117,7 @@ try: signed_message = crypto.sign(self._key, message, 'sha256') except TypeError: # Failed as str, so let's try with bytes (probably 0.14+) - message = bytes.decode(message) + message = str.encode(message) signed_message = crypto.sign(self._key, message, 'sha256') return signed_message @@ -146,7 +146,7 @@ try: pkey = crypto.load_pkcs12(key, password).get_privatekey() except TypeError: # Failed as str, so let's try with bytes (probably 0.14+) - password = bytes.decode(password) + message = str.encode(password) pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) From 287862496dbdfa51b4dd457e70b1e262e4538b24 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 22:02:47 -0700 Subject: [PATCH 39/69] Updating mox import --- tests/test_oauth2client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py index 2a3399a..a2c9f7e 100755 --- a/tests/test_oauth2client.py +++ b/tests/test_oauth2client.py @@ -24,7 +24,10 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import base64 import datetime -import mox +try: + from mox3 import mox +except ImportError: + import mox import os import time import unittest From c3dd9ac7d363eb9e9071bdc4222cd119d030cc4f Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Thu, 17 Jul 2014 22:02:59 -0700 Subject: [PATCH 40/69] Try to handle either str or bytes when verifying signature. --- oauth2client/crypt.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 9ba8099..0b97e75 100755 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -146,7 +146,7 @@ try: pkey = crypto.load_pkcs12(key, password).get_privatekey() except TypeError: # Failed as str, so let's try with bytes (probably 0.14+) - message = str.encode(password) + password = str.encode(password) pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) @@ -368,7 +368,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): if (len(segments) != 3): raise AppIdentityError( 'Wrong number of segments in token: %s' % jwt) - signed = str('%s.%s' % (segments[0], segments[1])) + signed = '%s.%s' % (segments[0], segments[1]) + try: + signed_bytes = str.encode(signed) + except TypeError: + signed_bytes = None + try: + signed_str = str(signed) + except TypeError: + signed_str = None signature = _urlsafe_b64decode(segments[2]) @@ -384,7 +392,12 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): verified = False for (keyname, pem) in certs.items(): verifier = Verifier.from_string(pem, True) - if (verifier.verify(signed, signature)): + # Python2 + if (verifier.verify(signed_str, signature)): + verified = True + break + # Python3 + if (verifier.verify(signed_bytes, signature)): verified = True break if not verified: From 05f0e55eabd897bdc0f57e13f0916edfab0b5a97 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Fri, 18 Jul 2014 09:01:14 -0700 Subject: [PATCH 41/69] Make sure private key (from JSON) is bytes --- oauth2client/client.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/oauth2client/client.py b/oauth2client/client.py index fb20828..b851fe9 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1328,6 +1328,11 @@ if HAS_CRYPTO: except TypeError: pass data = simplejson.loads(s) + try: + # Ensure it's bytes + data['private_key'] = str.encode(data['private_key']) + except TypeError: + pass retval = SignedJwtAssertionCredentials( data['service_account_name'], base64.b64decode(data['private_key']), From 55ca16d560a79e7c5dfef4728ec0e8f7bb77f051 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Fri, 18 Jul 2014 09:01:46 -0700 Subject: [PATCH 42/69] Fix pypy tox environments --- .travis.yml | 3 ++- tox.ini | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d674f8d..2ac7920 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,8 @@ env: - TOX_ENV=py32openssl14 - TOX_ENV=py33openssl13 - TOX_ENV=py33openssl14 - - TOX_ENV=pypy + - TOX_ENV=pypyopenssl13 + - TOX_ENV=pypyopenssl14 install: - pip install tox - pip install . diff --git a/tox.ini b/tox.ini index 9f3dbf4..52662b5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,10 @@ [tox] #envlist = py26, py27, py32, py33 -envlist = py26openssl13, py26openssl14, py27openssl13, py27openssl14, py32openssl13, py32openssl14, py33openssl13, py33openssl14 +envlist = py26openssl13, py26openssl14, + py27openssl13, py27openssl14, + py32openssl13, py32openssl14, + py33openssl13, py33openssl14, + pypyopenssl13, pypyopenssl14 [testenv] deps = keyring @@ -57,3 +61,11 @@ deps = {[testenv]deps} basepython = python3.3 deps = {[testenv]deps} pyopenssl==0.14 + +[testenv:pypyopenssl13] +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:pypyopenssl14] +deps = {[testenv]deps} + pyopenssl==0.14 From a2a06cfe10b05d5bd1728ebf46f90e0f3bf8a5f9 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Fri, 18 Jul 2014 13:34:25 -0700 Subject: [PATCH 43/69] Adding test cases for Python 3.4 --- .travis.yml | 2 ++ tox.ini | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2ac7920..fe80e8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ env: - TOX_ENV=py32openssl14 - TOX_ENV=py33openssl13 - TOX_ENV=py33openssl14 + - TOX_ENV=py34openssl13 + - TOX_ENV=py34openssl14 - TOX_ENV=pypyopenssl13 - TOX_ENV=pypyopenssl14 install: diff --git a/tox.ini b/tox.ini index 52662b5..eda8cfb 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = py26openssl13, py26openssl14, py27openssl13, py27openssl14, py32openssl13, py32openssl14, py33openssl13, py33openssl14, + py34openssl13, py34openssl14, pypyopenssl13, pypyopenssl14 [testenv] @@ -62,6 +63,16 @@ basepython = python3.3 deps = {[testenv]deps} pyopenssl==0.14 +[testenv:py34openssl13] +basepython = python3.4 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py34openssl14] +basepython = python3.4 +deps = {[testenv]deps} + pyopenssl==0.14 + [testenv:pypyopenssl13] deps = {[testenv]deps} pyopenssl<0.14 From 9a32de41f3d59fe4c229c4d0d9ccb54fccbb52fe Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Tue, 15 Jul 2014 11:04:53 -0700 Subject: [PATCH 44/69] Adding additional CI environments (specify branches) --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index 33f3af8..ec753fd 100644 --- a/tox.ini +++ b/tox.ini @@ -11,3 +11,9 @@ deps = keyring nose setenv = PYTHONPATH=../google_appengine commands = nosetests --ignore-files=test_appengine\.py + +# whitelist +branches: + only: + - master + - python3 \ No newline at end of file From 02d5f3523f0a5bbb4e02b0686cc3eb9f7c4b3e76 Mon Sep 17 00:00:00 2001 From: Pat Ferate Date: Mon, 21 Jul 2014 12:08:50 -0700 Subject: [PATCH 45/69] Adding test cases to master branch to see how it looks. Expecting a lot of red for Python3. --- .travis.yml | 17 +++++++++---- tox.ini | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39df0ef..3016859 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,22 @@ language: python python: 2.7 env: - - TOX_ENV=py26 - - TOX_ENV=py27 - - TOX_ENV=pypy + - TOX_ENV=py26openssl13 + - TOX_ENV=py26openssl14 + - TOX_ENV=py27openssl13 + - TOX_ENV=py27openssl14 + - TOX_ENV=py32openssl13 + - TOX_ENV=py32openssl14 + - TOX_ENV=py33openssl13 + - TOX_ENV=py33openssl14 + - TOX_ENV=py34openssl13 + - TOX_ENV=py34openssl14 + - TOX_ENV=pypyopenssl13 + - TOX_ENV=pypyopenssl14 install: - pip install tox - pip install . script: - tox -e $TOX_ENV notifications: - - email: false + email: false \ No newline at end of file diff --git a/tox.ini b/tox.ini index ec753fd..16e9a55 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,15 @@ [tox] -envlist = py26, py27 +#envlist = py26, py27, py32, py33 +envlist = py26openssl13, py26openssl14, + py27openssl13, py27openssl14, + py32openssl13, py32openssl14, + py33openssl13, py33openssl14, + py34openssl13, py34openssl14, + pypyopenssl13, pypyopenssl14 [testenv] deps = keyring mox - pyopenssl pycrypto==2.6 django==1.2 webtest @@ -16,4 +21,62 @@ commands = nosetests --ignore-files=test_appengine\.py branches: only: - master - - python3 \ No newline at end of file + - python3 + +[testenv:py26openssl13] +basepython = python2.6 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py26openssl14] +basepython = python2.6 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py27openssl13] +basepython = python2.7 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py27openssl14] +basepython = python2.7 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py32openssl13] +basepython = python3.2 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py32openssl14] +basepython = python3.2 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py33openssl13] +basepython = python3.3 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py33openssl14] +basepython = python3.3 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:py34openssl13] +basepython = python3.4 +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:py34openssl14] +basepython = python3.4 +deps = {[testenv]deps} + pyopenssl==0.14 + +[testenv:pypyopenssl13] +deps = {[testenv]deps} + pyopenssl<0.14 + +[testenv:pypyopenssl14] +deps = {[testenv]deps} + pyopenssl==0.14 From 1a0e6c7e4bb25492335ff80b5f34d8033b54f4d6 Mon Sep 17 00:00:00 2001 From: Craig Citro Date: Sun, 17 Aug 2014 10:06:10 -0700 Subject: [PATCH 46/69] Add note to README about py3. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f403139..44cc040 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ [![Build Status](https://travis-ci.org/google/oauth2client.svg?branch=master)](https://travis-ci.org/google/oauth2client) +NOTE +==== + +This is a work-in-progress branch to add python3 support to oauth2client. Most +of the work was done by @pferate. + This is a client library for accessing resources protected by OAuth 2.0. [Full documentation](http://google.github.io/oauth2client/) From 32b4faa1f01b2b6a27f82fa220e5f89c254647e6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 19 Aug 2014 11:11:53 +0900 Subject: [PATCH 47/69] Fix indent. --- oauth2client/client.py | 30 +++++++++++++++--------------- oauth2client/crypt.py | 16 ++++++++-------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 2368b03..a4e26e2 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -221,7 +221,7 @@ class Credentials(object): d['_module'] = t.__module__ for key in d.keys(): if isinstance(d[key], bytes): - d[key] = bytes.decode(d[key]) + d[key] = bytes.decode(d[key]) return simplejson.dumps(d) def to_json(self): @@ -246,11 +246,11 @@ class Credentials(object): to_json(). """ try: - # s: already str - data = simplejson.loads(s) + # s: already str + data = simplejson.loads(s) except TypeError: - # s: bytes -> str - data = simplejson.loads(bytes.decode(s)) + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) # Find and call the right classmethod from_json() to restore the object. module = data['_module'] try: @@ -586,11 +586,11 @@ class OAuth2Credentials(Credentials): An instance of a Credentials subclass. """ try: - # s: already str - data = simplejson.loads(s) + # s: already str + data = simplejson.loads(s) except TypeError: - # s: bytes -> str - data = simplejson.loads(bytes.decode(s)) + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) if 'token_expiry' in data and not isinstance(data['token_expiry'], datetime.datetime): try: @@ -880,14 +880,14 @@ class AccessTokenCredentials(OAuth2Credentials): @classmethod def from_json(cls, s): try: - # s: already str - data = simplejson.loads(s) + # s: already str + data = simplejson.loads(s) except TypeError: - # s: bytes -> str - data = simplejson.loads(bytes.decode(s)) + # s: bytes -> str + data = simplejson.loads(bytes.decode(s)) retval = AccessTokenCredentials( - data['access_token'], - data['user_agent']) + data['access_token'], + data['user_agent']) return retval def _refresh(self, http_request): diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 0b97e75..b062cc1 100755 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -114,11 +114,11 @@ try: """ message = str(message) try: - signed_message = crypto.sign(self._key, message, 'sha256') + signed_message = crypto.sign(self._key, message, 'sha256') except TypeError: - # Failed as str, so let's try with bytes (probably 0.14+) - message = str.encode(message) - signed_message = crypto.sign(self._key, message, 'sha256') + # Failed as str, so let's try with bytes (probably 0.14+) + message = str.encode(message) + signed_message = crypto.sign(self._key, message, 'sha256') return signed_message @staticmethod @@ -370,13 +370,13 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): 'Wrong number of segments in token: %s' % jwt) signed = '%s.%s' % (segments[0], segments[1]) try: - signed_bytes = str.encode(signed) + signed_bytes = str.encode(signed) except TypeError: - signed_bytes = None + signed_bytes = None try: - signed_str = str(signed) + signed_str = str(signed) except TypeError: - signed_str = None + signed_str = None signature = _urlsafe_b64decode(segments[2]) From 6e2b1f34fc7aabe5567a82fce3f25d7029299980 Mon Sep 17 00:00:00 2001 From: Dustin Farris Date: Mon, 18 Aug 2014 23:20:44 -0400 Subject: [PATCH 48/69] Convert OAuth2Credentials response content bytes to str if needed. --- oauth2client/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 2368b03..b3bf651 100755 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -757,8 +757,10 @@ class OAuth2Credentials(Credentials): resp, content = http_request( self.token_uri, method='POST', body=body, headers=headers) if resp.status == 200: - # TODO(jcgregorio) Raise an error if loads fails? - d = simplejson.loads(content) + try: + d = simplejson.loads(content) + except TypeError: + d = simplejson.loads(content.decode('utf-8')) self.token_response = d self.access_token = d['access_token'] self.refresh_token = d.get('refresh_token', self.refresh_token) From af79ce454246708a3eaa368f684db842dd3bd52d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Tue, 19 Aug 2014 17:34:41 +0900 Subject: [PATCH 49/69] Remove executable bit. --- oauth2client/client.py | 0 oauth2client/clientsecrets.py | 0 oauth2client/crypt.py | 1 - oauth2client/xsrfutil.py | 1 - 4 files changed, 2 deletions(-) mode change 100755 => 100644 oauth2client/client.py mode change 100755 => 100644 oauth2client/clientsecrets.py mode change 100755 => 100644 oauth2client/crypt.py mode change 100755 => 100644 oauth2client/xsrfutil.py diff --git a/oauth2client/client.py b/oauth2client/client.py old mode 100755 new mode 100644 diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py old mode 100755 new mode 100644 diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py old mode 100755 new mode 100644 index b062cc1..9b5172a --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -1,4 +1,3 @@ -#!/usr/bin/python2.4 # -*- coding: utf-8 -*- # # Copyright (C) 2011 Google Inc. diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py old mode 100755 new mode 100644 index f33e73b..ee25d85 --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -1,4 +1,3 @@ -#!/usr/bin/python2.5 # # Copyright 2010 the Melange authors. # From 6c3689e9e4d9ef368e0f43028519977e626bf88a Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 02:00:57 +0900 Subject: [PATCH 50/69] Fix errors. --- oauth2client/gce.py | 4 ++-- oauth2client/service_account.py | 2 +- tests/test_jwt.py | 1 + tests/test_service_account.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/oauth2client/gce.py b/oauth2client/gce.py index 59a8be0..747795b 100644 --- a/oauth2client/gce.py +++ b/oauth2client/gce.py @@ -21,7 +21,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import json import logging -import urllib +from six.moves import urllib from oauth2client import util from oauth2client.client import AccessTokenRefreshError @@ -78,7 +78,7 @@ class AppAssertionCredentials(AssertionCredentials): Raises: AccessTokenRefreshError: When the refresh fails. """ - query = '?scope=%s' % urllib.quote(self.scope, '') + query = '?scope=%s' % urllib.parse.quote(self.scope, '') uri = META.replace('{?scope}', query) response, content = http_request(uri) if response.status == 200: diff --git a/oauth2client/service_account.py b/oauth2client/service_account.py index 02f82e1..d8ccf1c 100644 --- a/oauth2client/service_account.py +++ b/oauth2client/service_account.py @@ -128,7 +128,7 @@ class _ServiceAccountCredentials(AssertionCredentials): def _urlsafe_b64encode(data): return base64.urlsafe_b64encode( - json.dumps(data, separators=(',', ':')).encode('UTF-8')).rstrip('=') + json.dumps(data, separators=(',', ':')).encode('UTF-8')).rstrip(b'=') def _get_private_key(private_key_pkcs8_text): """Get an RSA private key object from a pkcs8 representation.""" diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 6728460..913726e 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -23,6 +23,7 @@ Unit tests for oauth2client. __author__ = 'jcgregorio@google.com (Joe Gregorio)' import os +import sys import tempfile import time import unittest diff --git a/tests/test_service_account.py b/tests/test_service_account.py index caedfa0..79a0881 100644 --- a/tests/test_service_account.py +++ b/tests/test_service_account.py @@ -26,7 +26,7 @@ import rsa import time import unittest -from http_mock import HttpMockSequence +from .http_mock import HttpMockSequence from oauth2client.service_account import _ServiceAccountCredentials From 6587b47eadf9d9641f1143535bb5e04dde14ecd6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 02:09:22 +0900 Subject: [PATCH 51/69] Cleanup client.py --- oauth2client/client.py | 44 ++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index f9f0da3..69a5726 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -29,21 +29,7 @@ import os import sys import time import six -try: - from urllib.parse import urlparse, urlunparse, urlencode, parse_qsl - from urllib.request import urlopen - from urllib.error import URLError -except ImportError: - from urlparse import urlparse, urlunparse - from urllib import urlencode - from urllib2 import urlopen, URLError - try: - from urlparse import parse_qsl - except ImportError: - from cgi import parse_qsl - -if sys.version > '3': - long = int +from six.moves import urllib import httplib2 from oauth2client import GOOGLE_AUTH_URI @@ -412,11 +398,11 @@ def _update_query_params(uri, params): Returns: The same URI but with the new query parameters added. """ - parts = urlparse.urlparse(uri) - query_params = dict(urlparse.parse_qsl(parts.query)) + parts = urllib.parse.urlparse(uri) + query_params = dict(urllib.parse.parse_qsl(parts.query)) query_params.update(params) - new_parts = parts._replace(query=urllib.urlencode(query_params)) - return urlparse.urlunparse(new_parts) + new_parts = parts._replace(query=urllib.parse.urlencode(query_params)) + return urllib.parse.urlunparse(new_parts) class OAuth2Credentials(Credentials): @@ -693,7 +679,7 @@ class OAuth2Credentials(Credentials): def _generate_refresh_request_body(self): """Generate the body that will be used in the refresh request.""" - body = urlencode({ + body = urllib.parse.urlencode({ 'grant_type': 'refresh_token', 'client_id': self.client_id, 'client_secret': self.client_secret, @@ -908,7 +894,7 @@ class AccessTokenCredentials(OAuth2Credentials): _env_name = None -def _get_environment(urllib2_urlopen=None): +def _get_environment(urlopen=None): """Detect the environment the code is being run on.""" global _env_name @@ -923,14 +909,14 @@ def _get_environment(urllib2_urlopen=None): _env_name = 'GAE_LOCAL' else: try: - if urllib2_urlopen is None: - urllib2_urlopen = urlopen - response = urllib2_urlopen('http://metadata.google.internal') + if urlopen is None: + urlopen = urllib.request.urlopen + response = urlopen('http://metadata.google.internal') if any('Metadata-Flavor: Google' in h for h in response.info().headers): _env_name = 'GCE_PRODUCTION' else: _env_name = 'UNKNOWN' - except URLError: + except urllib.error.URLError: _env_name = 'UNKNOWN' return _env_name @@ -1284,7 +1270,7 @@ class AssertionCredentials(GoogleCredentials): def _generate_refresh_request_body(self): assertion = self._generate_assertion() - body = urlencode({ + body = urllib.parse.urlencode({ 'assertion': assertion, 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', }) @@ -1400,7 +1386,7 @@ if HAS_CRYPTO: def _generate_assertion(self): """Generate the assertion that will be used in the request.""" - now = long(time.time()) + now = int(time.time()) payload = { 'aud': self.token_uri, 'scope': self.scope, @@ -1500,7 +1486,7 @@ def _parse_exchange_token_response(content): except Exception: # different JSON libs raise different exceptions, # so we just do a catch-all here - resp = dict(urlparse.parse_qsl(content)) + resp = dict(urllib.parse.parse_qsl(content)) # some providers respond with 'expires', others with 'expires_in' if resp and 'expires' in resp: @@ -1714,7 +1700,7 @@ class OAuth2WebServerFlow(Flow): else: code = code['code'] - body = urlencode({ + body = urllib.parse.urlencode({ 'grant_type': 'authorization_code', 'client_id': self.client_id, 'client_secret': self.client_secret, From 73b8beebec3d1d1fd4a303205982822ab0e359d9 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 02:21:36 +0900 Subject: [PATCH 52/69] cleanup client.py --- oauth2client/client.py | 55 +++++++++++++----------------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 69a5726..c20a0df 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -206,9 +206,9 @@ class Credentials(object): # Add in information we will need later to reconsistitue this instance. d['_class'] = t.__name__ d['_module'] = t.__module__ - for key in d.keys(): - if isinstance(d[key], bytes): - d[key] = bytes.decode(d[key]) + for key, val in d.items(): + if isinstance(val, bytes): + d[key] = val.decode('utf-8') return json.dumps(d) def to_json(self): @@ -232,12 +232,9 @@ class Credentials(object): An instance of the subclass of Credentials that was serialized with to_json(). """ - try: - # s: already str - data = json.loads(s) - except TypeError: - # s: bytes -> str - data = json.loads(bytes.decode(s)) + if six.PY3 and isinstance(s, bytes): + s = s.decode('utf-8') + data = json.loads(s) # Find and call the right classmethod from_json() to restore the object. module = data['_module'] try: @@ -572,12 +569,9 @@ class OAuth2Credentials(Credentials): Returns: An instance of a Credentials subclass. """ - try: - # s: already str - data = json.loads(s) - except TypeError: - # s: bytes -> str - data = json.loads(bytes.decode(s)) + if six.PY3 and isinstance(s, bytes): + s = s.decode('utf-8') + data = json.loads(s) if 'token_expiry' in data and not isinstance(data['token_expiry'], datetime.datetime): try: @@ -866,12 +860,9 @@ class AccessTokenCredentials(OAuth2Credentials): @classmethod def from_json(cls, s): - try: - # s: already str - data = json.loads(s) - except TypeError: - # s: bytes -> str - data = json.loads(bytes.decode(s)) + if six.PY3 and isinstance(s, bytes): + s = s.decode('utf-8') + data = json.loads(s) retval = AccessTokenCredentials( data['access_token'], data['user_agent']) @@ -1348,11 +1339,8 @@ if HAS_CRYPTO: # Keep base64 encoded so it can be stored in JSON. self.private_key = base64.b64encode(private_key) - try: - # Ensure it's bytes - self.private_key = str.encode(self.private_key) - except TypeError: - pass + if isinstance(self.private_key, six.text_type): + self.private_key = self.private_key.encode('utf-8') self.private_key_password = private_key_password self.service_account_name = service_account_name @@ -1360,17 +1348,9 @@ if HAS_CRYPTO: @classmethod def from_json(cls, s): - try: - # Ensure it's a str - s = bytes.decode(s) - except TypeError: - pass + if six.PY3 and isinstance(s, bytes): + s = s.decode('utf-8') data = json.loads(s) - try: - # Ensure it's bytes - data['private_key'] = str.encode(data['private_key']) - except TypeError: - pass retval = SignedJwtAssertionCredentials( data['service_account_name'], base64.b64decode(data['private_key']), @@ -1433,8 +1413,7 @@ if HAS_CRYPTO: resp, content = http.request(cert_uri) if resp.status == 200: - content = bytes.decode(content) - certs = json.loads(content) + certs = json.loads(content.decode('utf-8')) return crypt.verify_signed_jwt_with_certs(id_token, certs, audience) else: raise VerifyJwtTokenError('Status code: %d' % resp.status) From 217c19100dc73cf5b7e35ce9e4fbecb44654d83a Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 02:25:21 +0900 Subject: [PATCH 53/69] Cleanup --- oauth2client/crypt.py | 5 +---- oauth2client/tools.py | 7 ++----- oauth2client/util.py | 19 +++++++------------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index b301be7..d50bd25 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -21,9 +21,6 @@ import logging import sys import time -if sys.version_info[0] >= 3: - long = int - CLOCK_SKEW_SECS = 300 # 5 minutes in seconds AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds @@ -407,7 +404,7 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): earliest = iat - CLOCK_SKEW_SECS # Check expiration timestamp. - now = long(time.time()) + now = int(time.time()) exp = parsed.get('exp') if exp is None: raise AppIdentityError('No exp field in token: %s' % json_body) diff --git a/oauth2client/tools.py b/oauth2client/tools.py index 409c320..c7e631b 100644 --- a/oauth2client/tools.py +++ b/oauth2client/tools.py @@ -30,10 +30,7 @@ import socket import sys import webbrowser -try: - from urllib.parse import parse_qsl -except ImportError: - from urlparse import parse_qsl +from six.moves import urllib from oauth2client import client from oauth2client import util @@ -93,7 +90,7 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.send_header("Content-type", "text/html") self.end_headers() query = self.path.split('?', 1)[-1] - query = dict(parse_qsl(query)) + query = dict(urllib.parse.parse_qsl(query)) self.server.query_params = query self.wfile.write("Authentication Status") self.wfile.write("

The authentication flow has completed.

") diff --git a/oauth2client/util.py b/oauth2client/util.py index 080f151..5f21d8b 100644 --- a/oauth2client/util.py +++ b/oauth2client/util.py @@ -34,14 +34,9 @@ import logging import sys import types -try: - from urllib.parse import urlparse, urlunparse, urlencode, parse_qsl -except ImportError: - from urlparse import urlparse, urlunparse, parse_qsl - from urllib import urlencode +import six +from six.moves import urllib -if sys.version > '3': - long = int logger = logging.getLogger(__name__) @@ -137,7 +132,7 @@ def positional(max_positional_args): return wrapped(*args, **kwargs) return positional_wrapper - if isinstance(max_positional_args, (int, long)): + if isinstance(max_positional_args, six.integer_types): return positional_decorator else: args, _, _, defaults = inspect.getargspec(max_positional_args) @@ -198,8 +193,8 @@ def _add_query_parameter(url, name, value): if value is None: return url else: - parsed = list(urlparse(url)) - q = dict(parse_qsl(parsed[4])) + parsed = list(urllib.parse.urlparse(url)) + q = dict(urllib.parse.parse_qsl(parsed[4])) q[name] = value - parsed[4] = urlencode(q) - return urlunparse(parsed) + parsed[4] = urllib.parse.urlencode(q) + return urllib.parse.urlunparse(parsed) From 6f705ce6d99d28baa27b4fa1ebd2e33494f52366 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 02:41:13 +0900 Subject: [PATCH 54/69] Use int instead of long. --- oauth2client/service_account.py | 6 +----- oauth2client/xsrfutil.py | 8 ++------ tests/test_jwt.py | 4 +--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/oauth2client/service_account.py b/oauth2client/service_account.py index d8ccf1c..25bbb98 100644 --- a/oauth2client/service_account.py +++ b/oauth2client/service_account.py @@ -21,10 +21,6 @@ import base64 import json import time -import sys -if sys.version > '3': - long = int - from oauth2client import GOOGLE_REVOKE_URI from oauth2client import GOOGLE_TOKEN_URI from oauth2client import util @@ -68,7 +64,7 @@ class _ServiceAccountCredentials(AssertionCredentials): 'kid': self._private_key_id } - now = long(time.time()) + now = int(time.time()) payload = { 'aud': self._token_uri, 'scope': self._scopes, diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py index d9eae7d..81ddf15 100644 --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -25,10 +25,6 @@ import base64 import hmac import time -import sys -if sys.version > '3': - long = int - from oauth2client import util @@ -98,13 +94,13 @@ def validate_token(key, token, user_id, action_id="", current_time=None): decoded = base64.urlsafe_b64decode(token) # Decode is needed for Python3 # It will fail for Python2 - token_time = long(decoded.decode(ENCODING).split(DELIMITER)[-1]) + token_time = int(decoded.decode(ENCODING).split(DELIMITER)[-1]) except (TypeError, ValueError): try: # Try again, in case it fails here decoded = base64.urlsafe_b64decode(token) # Decode is not needed for Python2 - token_time = long(decoded.split(DELIMITER)[-1]) + token_time = int(decoded.split(DELIMITER)[-1]) except (TypeError, ValueError): return False if current_time is None: diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 913726e..d3891b8 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -38,8 +38,6 @@ from oauth2client.client import HAS_OPENSSL from oauth2client.client import HAS_CRYPTO from oauth2client.file import Storage -if sys.version > '3': - long = int def datafile(filename): f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'rb') @@ -90,7 +88,7 @@ class CryptTests(unittest.TestCase): private_key = datafile('privatekey.%s' % self.format) signer = self.signer.from_string(private_key) audience = 'some_audience_address@testing.gserviceaccount.com' - now = long(time.time()) + now = int(time.time()) return crypt.make_signed_jwt(signer, { 'aud': audience, From 1668c60692395a726eaaddc0126b238506c5d116 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 11:59:29 +0900 Subject: [PATCH 55/69] Drop Python 3.2 support --- .travis.yml | 5 +---- tox.ini | 11 ----------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe80e8c..5ead14a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ env: - TOX_ENV=py26openssl14 - TOX_ENV=py27openssl13 - TOX_ENV=py27openssl14 - - TOX_ENV=py32openssl13 - - TOX_ENV=py32openssl14 - TOX_ENV=py33openssl13 - TOX_ENV=py33openssl14 - TOX_ENV=py34openssl13 @@ -15,8 +13,7 @@ env: - TOX_ENV=pypyopenssl14 install: - pip install tox - - pip install . script: - tox -e $TOX_ENV notifications: - email: false + - email: false diff --git a/tox.ini b/tox.ini index 45f86cb..8329997 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,6 @@ #envlist = py26, py27, py32, py33 envlist = py26openssl13, py26openssl14, py27openssl13, py27openssl14, - py32openssl13, py32openssl14, py33openssl13, py33openssl14, py34openssl13, py34openssl14, pypyopenssl13, pypyopenssl14 @@ -43,16 +42,6 @@ basepython = python2.7 deps = {[testenv]deps} pyopenssl==0.14 -[testenv:py32openssl13] -basepython = python3.2 -deps = {[testenv]deps} - pyopenssl<0.14 - -[testenv:py32openssl14] -basepython = python3.2 -deps = {[testenv]deps} - pyopenssl==0.14 - [testenv:py33openssl13] basepython = python3.3 deps = {[testenv]deps} From d09be0966bc650d40331ae208965f05cb4852277 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 12:19:06 +0900 Subject: [PATCH 56/69] HttpMockSequence should return bytes as content. --- tests/http_mock.py | 8 ++-- tests/test_appengine.py | 1 - tests/test_file.py | 1 - tests/test_jwt.py | 12 ++--- tests/test_oauth2client.py | 86 +++++++++++++++++------------------ tests/test_service_account.py | 4 +- 6 files changed, 55 insertions(+), 57 deletions(-) diff --git a/tests/http_mock.py b/tests/http_mock.py index 57b5f15..c24725f 100644 --- a/tests/http_mock.py +++ b/tests/http_mock.py @@ -67,9 +67,9 @@ class HttpMockSequence(object): and content and then use as if an httplib2.Http instance. http = HttpMockSequence([ - ({'status': '401'}, ''), - ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'), - ({'status': '200'}, 'echo_request_headers'), + ({'status': '401'}, b''), + ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), + ({'status': '200'}, b'echo_request_headers'), ]) resp, content = http.request("http://examples.com") @@ -98,6 +98,8 @@ class HttpMockSequence(object): redirections=1, connection_type=None): resp, content = self._iterable.pop(0) + if not isinstance(content, bytes): + raise TypeError("http content should be bytes: %r" % (content,)) if content == 'echo_request_headers': content = headers elif content == 'echo_request_headers_as_json': diff --git a/tests/test_appengine.py b/tests/test_appengine.py index 5514d84..c35680c 100644 --- a/tests/test_appengine.py +++ b/tests/test_appengine.py @@ -46,7 +46,6 @@ from google.appengine.ext import db from google.appengine.ext import ndb from google.appengine.ext import testbed from google.appengine.runtime import apiproxy_errors -from http_mock import HttpMockSequence from oauth2client import appengine from oauth2client import GOOGLE_TOKEN_URI from oauth2client.clientsecrets import _loadfile diff --git a/tests/test_file.py b/tests/test_file.py index fe1e014..1c34a06 100644 --- a/tests/test_file.py +++ b/tests/test_file.py @@ -32,7 +32,6 @@ import stat import tempfile import unittest -from .http_mock import HttpMockSequence from oauth2client import GOOGLE_TOKEN_URI from oauth2client import file from oauth2client import locked_file diff --git a/tests/test_jwt.py b/tests/test_jwt.py index d3891b8..e42876c 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -212,8 +212,8 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase): scope='read+write', sub='joe@example.org') http = HttpMockSequence([ - ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'), - ({'status': '200'}, 'echo_request_headers'), + ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), + ({'status': '200'}, b'echo_request_headers'), ]) http = credentials.authorize(http) _, content = http.request('http://example.org') @@ -235,10 +235,10 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase): def _credentials_refresh(self, credentials): http = HttpMockSequence([ - ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'), - ({'status': '401'}, ''), - ({'status': '200'}, '{"access_token":"3/3w","expires_in":3600}'), - ({'status': '200'}, 'echo_request_headers'), + ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), + ({'status': '401'}, b''), + ({'status': '200'}, b'{"access_token":"3/3w","expires_in":3600}'), + ({'status': '200'}, b'echo_request_headers'), ]) http = credentials.authorize(http) _, content = http.request('http://example.org') diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py index 21a4751..4bb3b5f 100755 --- a/tests/test_oauth2client.py +++ b/tests/test_oauth2client.py @@ -33,10 +33,7 @@ import os import time import unittest import six -try: - from urllib.parse import urlparse, parse_qs -except ImportError: - from urlparse import urlparse, parse_qs +from six.moves import urllib from .http_mock import HttpMock from .http_mock import HttpMockSequence @@ -85,15 +82,15 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') # googleapiclient.test_discovery; consolidate these definitions. def assertUrisEqual(testcase, expected, actual): """Test that URIs are the same, up to reordering of query parameters.""" - expected = urlparse(expected) - actual = urlparse(actual) + expected = urllib.parse.urlparse(expected) + actual = urllib.parse.urlparse(actual) testcase.assertEqual(expected.scheme, actual.scheme) testcase.assertEqual(expected.netloc, actual.netloc) testcase.assertEqual(expected.path, actual.path) testcase.assertEqual(expected.params, actual.params) testcase.assertEqual(expected.fragment, actual.fragment) - expected_query = parse_qs(expected.query) - actual_query = parse_qs(actual.query) + expected_query = urllib.parse.parse_qs(expected.query) + actual_query = urllib.parse.parse_qs(actual.query) for name in expected_query.keys(): testcase.assertEqual(expected_query[name], actual_query[name]) for name in actual_query.keys(): @@ -547,9 +544,9 @@ class BasicCredentialsTests(unittest.TestCase): for status_code in REFRESH_STATUS_CODES: token_response = {'access_token': '1/3w', 'expires_in': 3600} http = HttpMockSequence([ - ({'status': status_code}, ''), - ({'status': '200'}, json.dumps(token_response)), - ({'status': '200'}, 'echo_request_headers'), + ({'status': status_code}, b''), + ({'status': '200'}, json.dumps(token_response).encode('utf-8')), + ({'status': '200'}, b'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -560,8 +557,8 @@ class BasicCredentialsTests(unittest.TestCase): def test_token_refresh_failure(self): for status_code in REFRESH_STATUS_CODES: http = HttpMockSequence([ - ({'status': status_code}, ''), - ({'status': '400'}, '{"error":"access_denied"}'), + ({'status': status_code}, b''), + ({'status': '400'}, b'{"error":"access_denied"}'), ]) http = self.credentials.authorize(http) try: @@ -584,7 +581,7 @@ class BasicCredentialsTests(unittest.TestCase): def test_non_401_error_response(self): http = HttpMockSequence([ - ({'status': '400'}, ''), + ({'status': '400'}, b''), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -637,8 +634,8 @@ class BasicCredentialsTests(unittest.TestCase): token_response_first = {'access_token': 'first_token', 'expires_in': S} token_response_second = {'access_token': 'second_token', 'expires_in': S} http = HttpMockSequence([ - ({'status': '200'}, json.dumps(token_response_first)), - ({'status': '200'}, json.dumps(token_response_second)), + ({'status': '200'}, json.dumps(token_response_first).encode('utf-8')), + ({'status': '200'}, json.dumps(token_response_second).encode('utf-8')), ]) token = self.credentials.get_access_token(http=http) @@ -674,7 +671,7 @@ class AccessTokenCredentialsTests(unittest.TestCase): def test_token_refresh_success(self): for status_code in REFRESH_STATUS_CODES: http = HttpMockSequence([ - ({'status': status_code}, ''), + ({'status': status_code}, b''), ]) http = self.credentials.authorize(http) try: @@ -697,7 +694,7 @@ class AccessTokenCredentialsTests(unittest.TestCase): def test_non_401_error_response(self): http = HttpMockSequence([ - ({'status': '400'}, ''), + ({'status': '400'}, b''), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -705,7 +702,7 @@ class AccessTokenCredentialsTests(unittest.TestCase): def test_auth_header_sent(self): http = HttpMockSequence([ - ({'status': '200'}, 'echo_request_headers'), + ({'status': '200'}, b'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -727,15 +724,16 @@ class TestAssertionCredentials(unittest.TestCase): user_agent=user_agent) def test_assertion_body(self): - body = parse_qs(self.credentials._generate_refresh_request_body()) + body = urllib.parse.parse_qs( + self.credentials._generate_refresh_request_body()) self.assertEqual(self.assertion_text, body['assertion'][0]) self.assertEqual('urn:ietf:params:oauth:grant-type:jwt-bearer', body['grant_type'][0]) def test_assertion_refresh(self): http = HttpMockSequence([ - ({'status': '200'}, '{"access_token":"1/3w"}'), - ({'status': '200'}, 'echo_request_headers'), + ({'status': '200'}, b'{"access_token":"1/3w"}'), + ({'status': '200'}, b'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -799,8 +797,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_construct_authorize_url(self): authorize_url = self.flow.step1_get_authorize_url() - parsed = urlparse(authorize_url) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(authorize_url) + q = urllib.parse.parse_qs(parsed[4]) self.assertEqual('client_id+1', q['client_id'][0]) self.assertEqual('code', q['response_type'][0]) self.assertEqual('foo', q['scope'][0]) @@ -820,8 +818,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase): ) authorize_url = flow.step1_get_authorize_url() - parsed = urlparse(authorize_url) - q = parse_qs(parsed[4]) + parsed = urllib.parse.urlparse(authorize_url) + q = urllib.parse.parse_qs(parsed[4]) self.assertEqual('client_id+1', q['client_id'][0]) self.assertEqual('token', q['response_type'][0]) self.assertEqual('foo', q['scope'][0]) @@ -830,7 +828,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_exchange_failure(self): http = HttpMockSequence([ - ({'status': '400'}, '{"error":"invalid_request"}'), + ({'status': '400'}, b'{"error":"invalid_request"}'), ]) try: @@ -857,9 +855,9 @@ class OAuth2WebServerFlowTest(unittest.TestCase): # exceptions are being raised instead of FlowExchangeError. http = HttpMockSequence([ ({'status': '400'}, - """ {"error": { - "type": "OAuthException", - "message": "Error validating verification code."} }"""), + b""" {"error": { + "type": "OAuthException", + "message": "Error validating verification code."} }"""), ]) try: @@ -871,7 +869,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_exchange_success(self): http = HttpMockSequence([ ({'status': '200'}, - """{ "access_token":"SlAV32hkKG", + b"""{ "access_token":"SlAV32hkKG", "expires_in":3600, "refresh_token":"8xLOxBtZp8" }"""), ]) @@ -884,7 +882,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_urlencoded_exchange_success(self): http = HttpMockSequence([ - ({'status': '200'}, 'access_token=SlAV32hkKG&expires_in=3600'), + ({'status': '200'}, b'access_token=SlAV32hkKG&expires_in=3600'), ]) credentials = self.flow.step2_exchange('some random code', http=http) @@ -895,7 +893,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): http = HttpMockSequence([ # Note the 'expires=3600' where you'd normally # have if named 'expires_in' - ({'status': '200'}, 'access_token=SlAV32hkKG&expires=3600'), + ({'status': '200'}, b'access_token=SlAV32hkKG&expires=3600'), ]) credentials = self.flow.step2_exchange('some random code', http=http) @@ -903,7 +901,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_exchange_no_expires_in(self): http = HttpMockSequence([ - ({'status': '200'}, """{ "access_token":"SlAV32hkKG", + ({'status': '200'}, b"""{ "access_token":"SlAV32hkKG", "refresh_token":"8xLOxBtZp8" }"""), ]) @@ -914,7 +912,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): http = HttpMockSequence([ # This might be redundant but just to make sure # urlencoded access_token gets parsed correctly - ({'status': '200'}, 'access_token=SlAV32hkKG'), + ({'status': '200'}, b'access_token=SlAV32hkKG'), ]) credentials = self.flow.step2_exchange('some random code', http=http) @@ -922,7 +920,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_exchange_fails_if_no_code(self): http = HttpMockSequence([ - ({'status': '200'}, """{ "access_token":"SlAV32hkKG", + ({'status': '200'}, b"""{ "access_token":"SlAV32hkKG", "refresh_token":"8xLOxBtZp8" }"""), ]) @@ -935,7 +933,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase): def test_exchange_id_token_fail(self): http = HttpMockSequence([ - ({'status': '200'}, """{ "access_token":"SlAV32hkKG", + ({'status': '200'}, b"""{ "access_token":"SlAV32hkKG", "refresh_token":"8xLOxBtZp8", "id_token": "stuff.payload"}"""), ]) @@ -950,9 +948,9 @@ class OAuth2WebServerFlowTest(unittest.TestCase): base64.urlsafe_b64encode('signature')) http = HttpMockSequence([ - ({'status': '200'}, """{ "access_token":"SlAV32hkKG", + ({'status': '200'}, ("""{ "access_token":"SlAV32hkKG", "refresh_token":"8xLOxBtZp8", - "id_token": "%s"}""" % jwt), + "id_token": "%s"}""" % jwt).encode('utf-8')), ]) credentials = self.flow.step2_exchange('some random code', http=http) @@ -982,7 +980,7 @@ class CredentialsFromCodeTests(unittest.TestCase): token = 'asdfghjkl' payload = json.dumps({'access_token': token, 'expires_in': 3600}) http = HttpMockSequence([ - ({'status': '200'}, payload), + ({'status': '200'}, payload.encode('utf-8')), ]) credentials = credentials_from_code(self.client_id, self.client_secret, self.scope, self.code, redirect_uri=self.redirect_uri, @@ -992,7 +990,7 @@ class CredentialsFromCodeTests(unittest.TestCase): def test_exchange_code_for_token_fail(self): http = HttpMockSequence([ - ({'status': '400'}, '{"error":"invalid_request"}'), + ({'status': '400'}, b'{"error":"invalid_request"}'), ]) try: @@ -1006,7 +1004,7 @@ class CredentialsFromCodeTests(unittest.TestCase): def test_exchange_code_and_file_for_token(self): http = HttpMockSequence([ ({'status': '200'}, - """{ "access_token":"asdfghjkl", + b"""{ "access_token":"asdfghjkl", "expires_in":3600 }"""), ]) credentials = credentials_from_clientsecrets_and_code( @@ -1017,7 +1015,7 @@ class CredentialsFromCodeTests(unittest.TestCase): def test_exchange_code_and_cached_file_for_token(self): http = HttpMockSequence([ - ({'status': '200'}, '{ "access_token":"asdfghjkl"}'), + ({'status': '200'}, b'{ "access_token":"asdfghjkl"}'), ]) cache_mock = CacheMock() load_and_cache('client_secrets.json', 'some_secrets', cache_mock) @@ -1029,7 +1027,7 @@ class CredentialsFromCodeTests(unittest.TestCase): def test_exchange_code_and_file_for_token_fail(self): http = HttpMockSequence([ - ({'status': '400'}, '{"error":"invalid_request"}'), + ({'status': '400'}, b'{"error":"invalid_request"}'), ]) try: diff --git a/tests/test_service_account.py b/tests/test_service_account.py index 79a0881..83260b4 100644 --- a/tests/test_service_account.py +++ b/tests/test_service_account.py @@ -98,8 +98,8 @@ class ServiceAccountCredentialsTests(unittest.TestCase): token_response_first = {'access_token': 'first_token', 'expires_in': S} token_response_second = {'access_token': 'second_token', 'expires_in': S} http = HttpMockSequence([ - ({'status': '200'}, json.dumps(token_response_first)), - ({'status': '200'}, json.dumps(token_response_second)), + ({'status': '200'}, json.dumps(token_response_first).encode('utf-8')), + ({'status': '200'}, json.dumps(token_response_second).encode('utf-8')), ]) token = self.credentials.get_access_token(http=http) From c045addca917652fcd1ed3ed5a2ab9c567a6491c Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Wed, 20 Aug 2014 12:48:30 +0900 Subject: [PATCH 57/69] Fix http_mock --- tests/http_mock.py | 6 +++--- tests/test_jwt.py | 6 +++--- tests/test_oauth2client.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/http_mock.py b/tests/http_mock.py index c24725f..5c3a93e 100644 --- a/tests/http_mock.py +++ b/tests/http_mock.py @@ -69,7 +69,7 @@ class HttpMockSequence(object): http = HttpMockSequence([ ({'status': '401'}, b''), ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), - ({'status': '200'}, b'echo_request_headers'), + ({'status': '200'}, 'echo_request_headers'), ]) resp, content = http.request("http://examples.com") @@ -98,8 +98,6 @@ class HttpMockSequence(object): redirections=1, connection_type=None): resp, content = self._iterable.pop(0) - if not isinstance(content, bytes): - raise TypeError("http content should be bytes: %r" % (content,)) if content == 'echo_request_headers': content = headers elif content == 'echo_request_headers_as_json': @@ -111,4 +109,6 @@ class HttpMockSequence(object): content = body elif content == 'echo_request_uri': content = uri + elif not isinstance(content, bytes): + raise TypeError("http content should be bytes: %r" % (content,)) return httplib2.Response(resp), content diff --git a/tests/test_jwt.py b/tests/test_jwt.py index e42876c..6caf031 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -213,10 +213,10 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase): sub='joe@example.org') http = HttpMockSequence([ ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), - ({'status': '200'}, b'echo_request_headers'), + ({'status': '200'}, 'echo_request_headers'), ]) http = credentials.authorize(http) - _, content = http.request('http://example.org') + resp, content = http.request('http://example.org') self.assertEqual('Bearer 1/3w', content['Authorization']) def test_credentials_to_from_json(self): @@ -238,7 +238,7 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase): ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'), ({'status': '401'}, b''), ({'status': '200'}, b'{"access_token":"3/3w","expires_in":3600}'), - ({'status': '200'}, b'echo_request_headers'), + ({'status': '200'}, 'echo_request_headers'), ]) http = credentials.authorize(http) _, content = http.request('http://example.org') diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py index 4bb3b5f..6578a1d 100755 --- a/tests/test_oauth2client.py +++ b/tests/test_oauth2client.py @@ -546,7 +546,7 @@ class BasicCredentialsTests(unittest.TestCase): http = HttpMockSequence([ ({'status': status_code}, b''), ({'status': '200'}, json.dumps(token_response).encode('utf-8')), - ({'status': '200'}, b'echo_request_headers'), + ({'status': '200'}, 'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -702,7 +702,7 @@ class AccessTokenCredentialsTests(unittest.TestCase): def test_auth_header_sent(self): http = HttpMockSequence([ - ({'status': '200'}, b'echo_request_headers'), + ({'status': '200'}, 'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') @@ -733,7 +733,7 @@ class TestAssertionCredentials(unittest.TestCase): def test_assertion_refresh(self): http = HttpMockSequence([ ({'status': '200'}, b'{"access_token":"1/3w"}'), - ({'status': '200'}, b'echo_request_headers'), + ({'status': '200'}, 'echo_request_headers'), ]) http = self.credentials.authorize(http) resp, content = http.request('http://example.com') From ec1fb365ca7ca17885ff4fa97511c8810a7419e6 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 29 Aug 2014 19:28:39 +0900 Subject: [PATCH 58/69] Refactor xsrfutil --- oauth2client/xsrfutil.py | 63 ++++++++++++++++------------------------ tests/test_xsrfutil.py | 2 +- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/oauth2client/xsrfutil.py b/oauth2client/xsrfutil.py index 81ddf15..62d7afd 100644 --- a/oauth2client/xsrfutil.py +++ b/oauth2client/xsrfutil.py @@ -25,17 +25,27 @@ import base64 import hmac import time +import six from oauth2client import util # Delimiter character -DELIMITER = ':' +DELIMITER = b':' -ENCODING = 'utf-8' # 1 hour in seconds DEFAULT_TIMEOUT_SECS = 1*60*60 + +def _force_bytes(s): + if isinstance(s, bytes): + return s + s = str(s) + if isinstance(s, six.text_type): + return s.encode('utf-8') + return s + + @util.positional(2) def generate_token(key, user_id, action_id="", when=None): """Generates a URL-safe token for the given user, action, time tuple. @@ -51,22 +61,16 @@ def generate_token(key, user_id, action_id="", when=None): Returns: A string XSRF protection token. """ - when = when or int(time.time()) - decoded_key = '{key}{user_id}{delim}{action_id}{delim}{time}'.format(key=key, - user_id=user_id, - action_id=action_id, - delim=DELIMITER, - time=when).encode(ENCODING) - - digester = hmac.new(decoded_key) + when = _force_bytes(when or int(time.time())) + digester = hmac.new(_force_bytes(key)) + digester.update(_force_bytes(user_id)) + digester.update(DELIMITER) + digester.update(_force_bytes(action_id)) + digester.update(DELIMITER) + digester.update(when) digest = digester.digest() - decoded_token = '{digest}{delim}{time}'.format(digest=digest, delim=DELIMITER, time=when) - - try: - token = base64.urlsafe_b64encode(decoded_token.encode(ENCODING)) - except UnicodeDecodeError: - token = base64.urlsafe_b64encode(decoded_token) + token = base64.urlsafe_b64encode(digest + DELIMITER + when) return token @@ -92,17 +96,9 @@ def validate_token(key, token, user_id, action_id="", current_time=None): return False try: decoded = base64.urlsafe_b64decode(token) - # Decode is needed for Python3 - # It will fail for Python2 - token_time = int(decoded.decode(ENCODING).split(DELIMITER)[-1]) + token_time = int(decoded.split(DELIMITER)[-1]) except (TypeError, ValueError): - try: - # Try again, in case it fails here - decoded = base64.urlsafe_b64decode(token) - # Decode is not needed for Python2 - token_time = int(decoded.split(DELIMITER)[-1]) - except (TypeError, ValueError): - return False + return False if current_time is None: current_time = time.time() # If the token is too old it's not valid. @@ -117,15 +113,6 @@ def validate_token(key, token, user_id, action_id="", current_time=None): # Perform constant time comparison to avoid timing attacks different = 0 - try: - # Python3 - for x, y in zip(token, expected_token): - different |= x ^ y - except (TypeError, ValueError): - # Python2 - for x, y in zip(token.encode(ENCODING), expected_token.encode(ENCODING)): - different |= ord(x) ^ ord(y) - if different: - return False - - return True + for x, y in zip(bytearray(token), bytearray(expected_token)): + different |= x ^ y + return different == 0 diff --git a/tests/test_xsrfutil.py b/tests/test_xsrfutil.py index 409f77f..79877c5 100644 --- a/tests/test_xsrfutil.py +++ b/tests/test_xsrfutil.py @@ -96,7 +96,7 @@ class XsrfUtilTests(unittest.TestCase): # Invalid with extra garbage self.assertFalse(xsrfutil.validate_token(TEST_KEY, - token.decode('utf-8') + 'x', + token + b'x', TEST_USER_ID_1, action_id=TEST_ACTION_ID_1, current_time=later15mins)) From 89ceac0633c63343094f659a4c1f3ace014ace03 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 29 Aug 2014 19:29:00 +0900 Subject: [PATCH 59/69] keyring is optional dependency. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 0d62fa0..5bc3b23 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,6 @@ install_requires = [ 'pyasn1==0.1.7', 'pyasn1_modules==0.0.5', 'rsa==3.1.4', - 'keyring>=3.0', # Not completely sure the minimum version, but 3.0 (Sept 2013) should be sufficient ] long_desc = """The oauth2client is a client library for OAuth 2.0.""" From be82055d09c44a0e9b7bcd0ab1641739e11a8087 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 29 Aug 2014 19:32:39 +0900 Subject: [PATCH 60/69] Python 2.6 has io.StringIO --- tests/test_clientsecrets.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_clientsecrets.py b/tests/test_clientsecrets.py index b21ccad..6211424 100644 --- a/tests/test_clientsecrets.py +++ b/tests/test_clientsecrets.py @@ -19,10 +19,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import os import unittest -try: - from io import StringIO -except ImportError: - from StringIO import StringIO +from io import StringIO import httplib2 From 97fae56a719eb96d5656029f7672f5331199f4af Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 30 Aug 2014 19:32:34 +0900 Subject: [PATCH 61/69] cleanup --- oauth2client/client.py | 11 ++---- oauth2client/clientsecrets.py | 21 +++-------- oauth2client/crypt.py | 69 +++++++++++------------------------ tests/test_jwt.py | 2 +- 4 files changed, 32 insertions(+), 71 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 19e7914..217c99f 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1422,7 +1422,8 @@ if HAS_CRYPTO: def _urlsafe_b64decode(b64string): # Guard against unicode strings, which base64 can't handle. - b64string = b64string.encode('ascii') + if isinstance(b64string, six.text_type): + b64string = b64string.encode('ascii') padded = b64string + '=' * (4 - len(b64string) % 4) return base64.urlsafe_b64decode(padded) @@ -1664,13 +1665,7 @@ class OAuth2WebServerFlow(Flow): refresh_token. """ - try: - # Python2 - is_string = isinstance(code, basestring) - except NameError: - # Python3 - is_string = isinstance(code, str) - if not is_string: + if not isinstance(code, six.string_types): if 'code' not in code: if 'error' in code: error_msg = code['error'] diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py index 5f919ff..78680d1 100644 --- a/oauth2client/clientsecrets.py +++ b/oauth2client/clientsecrets.py @@ -21,6 +21,7 @@ an OAuth 2.0 protected service. __author__ = 'jcgregorio@google.com (Joe Gregorio)' import json +import six # Properties that make a client_secrets.json file valid. @@ -70,12 +71,9 @@ class InvalidClientSecretsError(Error): def _validate_clientsecrets(obj): if obj is None or len(obj) != 1: raise InvalidClientSecretsError('Invalid file format.') - try: - client_type = obj.keys()[0] - except TypeError: - client_type = list(obj.keys())[0] - if client_type not in VALID_CLIENT.keys(): - raise InvalidClientSecretsError('Unknown client type: %s.' % client_type) + client_type = tuple(obj)[0] + if client_type not in VALID_CLIENT: + raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type,)) client_info = obj[client_type] for prop_name in VALID_CLIENT[client_type]['required']: if prop_name not in client_info: @@ -101,11 +99,8 @@ def loads(s): def _loadfile(filename): try: - fp = open(filename, 'r') - try: + with open(filename, 'r') as fp: obj = json.load(fp) - finally: - fp.close() except IOError: raise InvalidClientSecretsError('File not found: "%s"' % filename) return _validate_clientsecrets(obj) @@ -153,8 +148,4 @@ def loadfile(filename, cache=None): obj = {client_type: client_info} cache.set(filename, obj, namespace=_SECRET_NAMESPACE) - try: - items = obj.iteritems().next() - except AttributeError: - items = next(iter(obj.items())) - return items + return next(six.iteritems(obj)) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index d50bd25..4183905 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -21,6 +21,8 @@ import logging import sys import time +import six + CLOCK_SKEW_SECS = 300 # 5 minutes in seconds AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds @@ -60,6 +62,8 @@ try: key that this object was constructed with. """ try: + if isinstance(message, six.text_type): + message = message.encode('utf-8') crypto.verify(self._pubkey, signature, message, 'sha256') return True except: @@ -102,22 +106,17 @@ try: """Signs a message. Args: - message: string, Message to be signed. + message: bytes, Message to be signed. Returns: string, The signature of the message for the given key. """ - message = str(message) - try: - signed_message = crypto.sign(self._key, message, 'sha256') - except TypeError: - # Failed as str, so let's try with bytes (probably 0.14+) - message = str.encode(message) - signed_message = crypto.sign(self._key, message, 'sha256') - return signed_message + if isinstance(message, six.text_type): + message = message.encode('utf-8') + return crypto.sign(self._key, message, 'sha256') @staticmethod - def from_string(key, password='notasecret'): + def from_string(key, password=b'notasecret'): """Construct a Signer instance from a string. Args: @@ -134,15 +133,9 @@ try: if parsed_pem_key: pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key) else: - # OpenSSL 0.13 needs password to be str - # OpenSSL 0.14 needs password to be bytes - password = str(password) - try: - pkey = crypto.load_pkcs12(key, password).get_privatekey() - except TypeError: - # Failed as str, so let's try with bytes (probably 0.14+) - password = str.encode(password) - pkey = crypto.load_pkcs12(key, password).get_privatekey() + if isinstance(password, six.text_type): + password = password.encode('utf-8') + pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) except ImportError: @@ -228,10 +221,8 @@ try: Returns: string, The signature of the message for the given key. """ - try: - message = str.encode(message) - except TypeError: - pass + if isinstance(message, six.text_type): + message = message.encode('utf-8') return PKCS1_v1_5.new(self._key).sign(SHA256.new(message)) @staticmethod @@ -294,17 +285,15 @@ def _parse_pem_key(raw_key_input): return None def _urlsafe_b64encode(raw_bytes): - # Make sure our bytes are actually bytes - try: - raw_bytes = str.encode(raw_bytes) - except (TypeError, UnicodeDecodeError): - pass - return bytes.decode(base64.urlsafe_b64encode(raw_bytes)).rstrip('=') + if isinstance(raw_bytes, six.text_type): + raw_bytes = raw_bytes.encode('utf-8') + return base64.urlsafe_b64encode(raw_bytes).decode('ascii').rstrip('=') def _urlsafe_b64decode(b64string): # Guard against unicode strings, which base64 can't handle. - b64string = b64string.encode('ascii') + if isinstance(b64string, six.text_type): + b64string = b64string.encode('ascii') padded = b64string + b'=' * (4 - len(b64string) % 4) return base64.urlsafe_b64decode(padded) @@ -363,35 +352,21 @@ def verify_signed_jwt_with_certs(jwt, certs, audience): if len(segments) != 3: raise AppIdentityError('Wrong number of segments in token: %s' % jwt) signed = '%s.%s' % (segments[0], segments[1]) - try: - signed_bytes = str.encode(signed) - except TypeError: - signed_bytes = None - try: - signed_str = str(signed) - except TypeError: - signed_str = None signature = _urlsafe_b64decode(segments[2]) # Parse token. json_body = _urlsafe_b64decode(segments[1]) try: - json_body = bytes.decode(json_body) - parsed = json.loads(json_body) + parsed = json.loads(json_body.decode('utf-8')) except: raise AppIdentityError('Can\'t parse token: %s' % json_body) # Check signature. verified = False - for _, pem in certs.items(): + for pem in certs.values(): verifier = Verifier.from_string(pem, True) - # Python2 - if verifier.verify(signed_str, signature): - verified = True - break - # Python3 - if verifier.verify(signed_bytes, signature): + if verifier.verify(signed, signature): verified = True break if not verified: diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 6caf031..8ed8e3f 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -82,7 +82,7 @@ class CryptTests(unittest.TestCase): crypt.verify_signed_jwt_with_certs(jwt, certs, audience) self.fail() except crypt.AppIdentityError as e: - self.assertTrue(expected_error in str(e)) + self.assertIn(expected_error, str(e)) def _create_signed_jwt(self): private_key = datafile('privatekey.%s' % self.format) From ca338243d9d0ff9c7155f0187827b891ecc3f286 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 30 Aug 2014 19:36:56 +0900 Subject: [PATCH 62/69] Fix broken license header. --- oauth2client/locked_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2client/locked_file.py b/oauth2client/locked_file.py index 78f77ad..f1b77d0 100644 --- a/oauth2client/locked_file.py +++ b/oauth2client/locked_file.py @@ -1,6 +1,6 @@ # Copyright 2011 Google Inc. # -# Licensed under the Ap ache License, Version 2.0 (the "License"); +# 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 # From 7d0d799cd108a2a2bc17697ce2617c9823f99b76 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 31 Aug 2014 03:14:45 +0900 Subject: [PATCH 63/69] Fix python 2.6 --- tests/test_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 8ed8e3f..c1c79ba 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -82,7 +82,7 @@ class CryptTests(unittest.TestCase): crypt.verify_signed_jwt_with_certs(jwt, certs, audience) self.fail() except crypt.AppIdentityError as e: - self.assertIn(expected_error, str(e)) + self.assert(expected_error in str(e)) def _create_signed_jwt(self): private_key = datafile('privatekey.%s' % self.format) From f771a97aef29265bca78123169b2f3930d8e156e Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 31 Aug 2014 03:19:07 +0900 Subject: [PATCH 64/69] Requires pyopenssl>=0.14 on Python 3. --- tox.ini | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/tox.ini b/tox.ini index 8329997..5c48fb6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] -#envlist = py26, py27, py32, py33 envlist = py26openssl13, py26openssl14, py27openssl13, py27openssl14, - py33openssl13, py33openssl14, - py34openssl13, py34openssl14, + py33openssl14, + py34openssl14, pypyopenssl13, pypyopenssl14 [testenv] @@ -42,21 +41,11 @@ basepython = python2.7 deps = {[testenv]deps} pyopenssl==0.14 -[testenv:py33openssl13] -basepython = python3.3 -deps = {[testenv]deps} - pyopenssl<0.14 - [testenv:py33openssl14] basepython = python3.3 deps = {[testenv]deps} pyopenssl==0.14 -[testenv:py34openssl13] -basepython = python3.4 -deps = {[testenv]deps} - pyopenssl<0.14 - [testenv:py34openssl14] basepython = python3.4 deps = {[testenv]deps} From 9d2dbe8cfd7c05b1559ee3606596c762f5204b08 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 31 Aug 2014 03:26:09 +0900 Subject: [PATCH 65/69] s/assert/assetTrue/ --- tests/test_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index c1c79ba..6caf031 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -82,7 +82,7 @@ class CryptTests(unittest.TestCase): crypt.verify_signed_jwt_with_certs(jwt, certs, audience) self.fail() except crypt.AppIdentityError as e: - self.assert(expected_error in str(e)) + self.assertTrue(expected_error in str(e)) def _create_signed_jwt(self): private_key = datafile('privatekey.%s' % self.format) From 0740f0ab2eec567361157d89433d02989213471e Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 31 Aug 2014 03:35:33 +0900 Subject: [PATCH 66/69] Remove py3[34]openssl13 from travis --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5ead14a..a30cb68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,7 @@ env: - TOX_ENV=py26openssl14 - TOX_ENV=py27openssl13 - TOX_ENV=py27openssl14 - - TOX_ENV=py33openssl13 - TOX_ENV=py33openssl14 - - TOX_ENV=py34openssl13 - TOX_ENV=py34openssl14 - TOX_ENV=pypyopenssl13 - TOX_ENV=pypyopenssl14 From 28561445e2f1c0e39df1a42c68ee9c048899e904 Mon Sep 17 00:00:00 2001 From: takuya sato Date: Thu, 11 Sep 2014 17:40:57 +0900 Subject: [PATCH 67/69] fix bytes-str conversion --- oauth2client/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 02f7762..62e1765 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1429,7 +1429,7 @@ def _urlsafe_b64decode(b64string): # Guard against unicode strings, which base64 can't handle. if isinstance(b64string, six.text_type): b64string = b64string.encode('ascii') - padded = b64string + '=' * (4 - len(b64string) % 4) + padded = b64string + '='.encode('ascii') * (4 - len(b64string) % 4) return base64.urlsafe_b64decode(padded) @@ -1450,7 +1450,7 @@ def _extract_id_token(id_token): raise VerifyJwtTokenError( 'Wrong number of segments in token: %s' % id_token) - return json.loads(_urlsafe_b64decode(segments[1])) + return json.loads(_urlsafe_b64decode(segments[1]).decode('utf-8')) def _parse_exchange_token_response(content): @@ -1468,7 +1468,7 @@ def _parse_exchange_token_response(content): """ resp = {} try: - resp = json.loads(content) + resp = json.loads(content.decode('utf-8')) except Exception: # different JSON libs raise different exceptions, # so we just do a catch-all here From 55a2525570ea1d41e88d708c09325a4fc64d2748 Mon Sep 17 00:00:00 2001 From: takuya sato Date: Thu, 11 Sep 2014 18:05:39 +0900 Subject: [PATCH 68/69] use bytes literal --- oauth2client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 62e1765..71e2669 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1429,7 +1429,7 @@ def _urlsafe_b64decode(b64string): # Guard against unicode strings, which base64 can't handle. if isinstance(b64string, six.text_type): b64string = b64string.encode('ascii') - padded = b64string + '='.encode('ascii') * (4 - len(b64string) % 4) + padded = b64string + b'=' * (4 - len(b64string) % 4) return base64.urlsafe_b64decode(padded) From f5de2afe670b12ed9ac9d99e20f86e71896f103d Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Fri, 10 Oct 2014 18:36:09 +0900 Subject: [PATCH 69/69] Fix some trivial issues. --- oauth2client/client.py | 2 +- oauth2client/crypt.py | 6 ++++-- tests/test_jwt.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 174e13e..4c8af28 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -1396,7 +1396,7 @@ class SignedJwtAssertionCredentials(AssertionCredentials): def _generate_assertion(self): """Generate the assertion that will be used in the request.""" - now = long(time.time()) + now = int(time.time()) payload = { 'aud': self.token_uri, 'scope': self.scope, diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 67b75b9..1c5748e 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -191,8 +191,10 @@ try: Verifier instance. """ if is_x509_cert: - pemLines = key_pem.replace(' ', '').split() - certDer = _urlsafe_b64decode(''.join(pemLines[1:-1])) + if isinstance(key_pem, six.text_type): + key_pem = key_pem.encode('ascii') + pemLines = key_pem.replace(b' ', b'').split() + certDer = _urlsafe_b64decode(b''.join(pemLines[1:-1])) certSeq = DerSequence() certSeq.decode(certDer) tbsSeq = DerSequence() diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 6cbbf4f..169e5f8 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -28,7 +28,7 @@ import tempfile import time import unittest -from http_mock import HttpMockSequence +from .http_mock import HttpMockSequence from oauth2client import crypt from oauth2client.client import Credentials from oauth2client.client import SignedJwtAssertionCredentials