Merge branch 'python3' into merge

This commit is contained in:
Craig Citro
2014-11-04 11:17:01 -08:00
26 changed files with 361 additions and 234 deletions

View File

@@ -1,12 +1,16 @@
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=py33openssl14
- TOX_ENV=py34openssl14
- TOX_ENV=pypyopenssl13
- TOX_ENV=pypyopenssl14
install:
- pip install tox
- pip install .
script:
- tox -e $TOX_ENV
notifications:

View File

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

View File

@@ -28,8 +28,8 @@ import logging
import os
import sys
import time
import urllib
import urlparse
import six
from six.moves import urllib
import httplib2
from oauth2client import clientsecrets
@@ -231,6 +231,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, val in d.items():
if isinstance(val, bytes):
d[key] = val.decode('utf-8')
return json.dumps(d)
def to_json(self):
@@ -254,6 +257,8 @@ class Credentials(object):
An instance of the subclass of Credentials that was serialized with
to_json().
"""
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']
@@ -398,7 +403,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)
@@ -415,11 +420,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):
@@ -589,6 +594,8 @@ class OAuth2Credentials(Credentials):
Returns:
An instance of a Credentials subclass.
"""
if six.PY3 and isinstance(s, bytes):
s = s.decode('utf-8')
data = json.loads(s)
if (data.get('token_expiry') and
not isinstance(data['token_expiry'], datetime.datetime)):
@@ -691,7 +698,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 = urllib.parse.urlencode({
'grant_type': 'refresh_token',
'client_id': self.client_id,
'client_secret': self.client_secret,
@@ -755,8 +762,9 @@ class OAuth2Credentials(Credentials):
logger.info('Refreshing access_token')
resp, content = http_request(
self.token_uri, method='POST', body=body, headers=headers)
if six.PY3:
content = content.decode('utf-8')
if resp.status == 200:
# TODO(jcgregorio) Raise an error if loads fails?
d = json.loads(content)
self.token_response = d
self.access_token = d['access_token']
@@ -785,7 +793,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)
@@ -822,7 +830,7 @@ class OAuth2Credentials(Credentials):
d = json.loads(content)
if 'error' in d:
error_msg = d['error']
except StandardError:
except (TypeError, ValueError):
pass
raise TokenRevokeError(error_msg)
@@ -880,10 +888,12 @@ class AccessTokenCredentials(OAuth2Credentials):
@classmethod
def from_json(cls, 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'])
data['access_token'],
data['user_agent'])
return retval
def _refresh(self, http_request):
@@ -903,7 +913,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
@@ -917,16 +927,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
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 urllib2.URLError:
except urllib.error.URLError:
_env_name = 'UNKNOWN'
return _env_name
@@ -956,7 +965,7 @@ class GoogleCredentials(OAuth2Credentials):
request = service.instances().list(project=PROJECT, zone=ZONE)
response = request.execute()
print response
print(response)
</code>
A service that does not require authentication does not need credentials
@@ -970,7 +979,7 @@ class GoogleCredentials(OAuth2Credentials):
request = service.apis().list()
response = request.execute()
print response
print(response)
</code>
"""
@@ -1168,7 +1177,7 @@ def _get_application_default_credential_from_file(
application_default_credential_filename):
"""Build the Application Default Credentials from file."""
import service_account
from oauth2client import service_account
# read the credentials from the file
with open(application_default_credential_filename) as (
@@ -1274,7 +1283,7 @@ class AssertionCredentials(GoogleCredentials):
def _generate_refresh_request_body(self):
assertion = self._generate_assertion()
body = urllib.urlencode({
body = urllib.parse.urlencode({
'assertion': assertion,
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
})
@@ -1363,6 +1372,8 @@ class SignedJwtAssertionCredentials(AssertionCredentials):
# Keep base64 encoded so it can be stored in JSON.
self.private_key = base64.b64encode(private_key)
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
@@ -1386,7 +1397,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,
@@ -1435,7 +1446,7 @@ def verify_id_token(id_token, audience, http=None,
resp, content = http.request(cert_uri)
if resp.status == 200:
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)
@@ -1443,8 +1454,9 @@ def verify_id_token(id_token, audience, http=None,
def _urlsafe_b64decode(b64string):
# Guard against unicode strings, which base64 can't handle.
b64string = b64string.encode('ascii')
padded = b64string + '=' * (4 - len(b64string) % 4)
if isinstance(b64string, six.text_type):
b64string = b64string.encode('ascii')
padded = b64string + b'=' * (4 - len(b64string) % 4)
return base64.urlsafe_b64decode(padded)
@@ -1465,7 +1477,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):
@@ -1483,11 +1495,11 @@ def _parse_exchange_token_response(content):
"""
resp = {}
try:
resp = json.loads(content)
except StandardError:
resp = json.loads(content.decode('utf-8'))
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:
@@ -1810,7 +1822,7 @@ class OAuth2WebServerFlow(Flow):
else:
post_data['grant_type'] = 'authorization_code'
post_data['redirect_uri'] = self.redirect_uri
body = urllib.urlencode(post_data)
body = urllib.parse.urlencode(post_data)
headers = {
'content-type': 'application/x-www-form-urlencoded',
}
@@ -1851,7 +1863,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)

View File

@@ -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,9 +71,9 @@ 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]
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:
@@ -98,11 +99,8 @@ def loads(s):
def _loadfile(filename):
try:
fp = file(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)
@@ -150,4 +148,4 @@ def loadfile(filename, cache=None):
obj = {client_type: client_info}
cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
return obj.iteritems().next()
return next(six.iteritems(obj))

View File

@@ -18,8 +18,11 @@
import base64
import json
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
@@ -59,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:
@@ -101,15 +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.
"""
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:
@@ -126,7 +133,9 @@ try:
if parsed_pem_key:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
else:
pkey = crypto.load_pkcs12(key, password.encode('utf8')).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:
@@ -182,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()
@@ -214,6 +225,8 @@ try:
Returns:
string, The signature of the message for the given key.
"""
if isinstance(message, six.text_type):
message = message.encode('utf-8')
return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
@staticmethod
@@ -269,19 +282,22 @@ 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:]
def _urlsafe_b64encode(raw_bytes):
return 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')
padded = b64string + '=' * (4 - len(b64string) % 4)
if isinstance(b64string, six.text_type):
b64string = b64string.encode('ascii')
padded = b64string + b'=' * (4 - len(b64string) % 4)
return base64.urlsafe_b64decode(padded)
@@ -345,13 +361,13 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
# Parse token.
json_body = _urlsafe_b64decode(segments[1])
try:
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)
if verifier.verify(signed, signature):
verified = True
@@ -366,7 +382,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)

View File

@@ -90,7 +90,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:
@@ -108,7 +108,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()

View File

@@ -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,13 +78,13 @@ 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:
try:
d = json.loads(content)
except StandardError as e:
except Exception as e:
raise AccessTokenRefreshError(str(e))
self.access_token = d['accessToken']
else:

View File

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

View File

@@ -191,7 +191,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
@@ -269,7 +269,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:

View File

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

View File

@@ -64,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,
@@ -77,14 +77,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'))
@@ -119,7 +125,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):

View File

@@ -28,12 +28,14 @@ import BaseHTTPServer
import logging
import socket
import sys
import urlparse
import webbrowser
from six.moves import urllib
from oauth2client import client
from oauth2client import util
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
To make this sample run you will need to populate the client_secrets.json file
@@ -88,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(urlparse.parse_qsl(query))
query = dict(urllib.parse.parse_qsl(query))
self.server.query_params = query
self.wfile.write("<html><head><title>Authentication Status</title></head>")
self.wfile.write("<body><p>The authentication flow has completed.</p>")
@@ -155,20 +157,20 @@ def run_flow(flow, storage, flags, http=None):
try:
httpd = ClientRedirectServer((flags.auth_host_name, port),
ClientRedirectHandler)
except socket.error as e:
except socket.error:
pass
else:
success = True
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)
@@ -179,20 +181,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:
@@ -202,7 +204,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()
@@ -214,7 +216,7 @@ def run_flow(flow, storage, flags, http=None):
storage.put(credential)
credential.set_store(storage)
print 'Authentication successful.'
print('Authentication successful.')
return credential

View File

@@ -31,9 +31,12 @@ __all__ = [
import inspect
import logging
import sys
import types
import urllib
import urlparse
import six
from six.moves import urllib
logger = logging.getLogger(__name__)
@@ -129,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)
@@ -149,7 +152,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)
@@ -186,8 +193,8 @@ def _add_query_parameter(url, name, value):
if value is None:
return url
else:
parsed = list(urlparse.urlparse(url))
q = dict(urlparse.parse_qsl(parsed[4]))
parsed = list(urllib.parse.urlparse(url))
q = dict(urllib.parse.parse_qsl(parsed[4]))
q[name] = value
parsed[4] = urllib.urlencode(q)
return urlparse.urlunparse(parsed)
parsed[4] = urllib.parse.urlencode(q)
return urllib.parse.urlunparse(parsed)

View File

@@ -1,4 +1,3 @@
#!/usr/bin/python2.5
#
# Copyright 2014 the Melange authors.
#
@@ -26,15 +25,27 @@ import base64
import hmac
import time
import six
from oauth2client import util
# Delimiter character
DELIMITER = ':'
DELIMITER = b':'
# 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.
@@ -50,18 +61,16 @@ def generate_token(key, user_id, action_id="", when=None):
Returns:
A string XSRF protection token.
"""
when = when or int(time.time())
digester = hmac.new(key)
digester.update(str(user_id))
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(action_id)
digester.update(_force_bytes(action_id))
digester.update(DELIMITER)
digester.update(str(when))
digester.update(when)
digest = digester.digest()
token = base64.urlsafe_b64encode('%s%s%d' % (digest,
DELIMITER,
when))
token = base64.urlsafe_b64encode(digest + DELIMITER + when)
return token
@@ -86,8 +95,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 = int(decoded.split(DELIMITER)[-1])
except (TypeError, ValueError):
return False
if current_time is None:
@@ -104,9 +113,6 @@ 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)
if different:
return False
return True
for x, y in zip(bytearray(token), bytearray(expected_token)):
different |= x ^ y
return different == 0

View File

@@ -67,8 +67,8 @@ 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': '401'}, b''),
({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
({'status': '200'}, 'echo_request_headers'),
])
resp, content = http.request("http://examples.com")
@@ -111,4 +111,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

View File

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

View File

@@ -19,7 +19,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import os
import unittest
import StringIO
from io import StringIO
import httplib2
@@ -57,6 +57,11 @@ class OAuth2CredentialsTests(unittest.TestCase):
""", 'Property'),
]
for src, match in ERRORS:
# Ensure that it is unicode
try:
src = src.decode('utf-8')
except AttributeError:
pass
# Test load(s)
try:
clientsecrets.loads(src)
@@ -66,7 +71,7 @@ class OAuth2CredentialsTests(unittest.TestCase):
# 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 as e:
@@ -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__':

View File

@@ -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'] = django_settings = imp.new_module('django_settings')
django_settings.SECRET_KEY = 'xyzzy'

View File

@@ -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
@@ -41,6 +40,11 @@ from oauth2client import util
from oauth2client.client import AccessTokenCredentials
from oauth2client.client import AssertionCredentials
from oauth2client.client import OAuth2Credentials
try:
# Python2
from future_builtins import oct
except:
pass
FILENAME = tempfile.mktemp('oauth2client_test.data')
@@ -96,7 +100,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()
@@ -154,13 +158,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,
@@ -171,7 +175,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'):
@@ -221,7 +225,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()

View File

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

View File

@@ -23,11 +23,12 @@ Unit tests for oauth2client.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import os
import sys
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
@@ -39,7 +40,7 @@ from oauth2client.file import Storage
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
@@ -67,11 +68,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 signature'))
self.assertFalse(verifier.verify(b'bar', signature))
self.assertFalse(verifier.verify(b'foo', 'bad signagure'))
def _check_jwt_failure(self, jwt, expected_error):
public_key = datafile('publickey.pem')
@@ -88,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,
@@ -212,11 +212,11 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase):
scope='read+write',
sub='joe@example.org')
http = HttpMockSequence([
({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
({'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):
@@ -235,9 +235,9 @@ 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'}, b'{"access_token":"1/3w","expires_in":3600}'),
({'status': '401'}, b''),
({'status': '200'}, b'{"access_token":"3/3w","expires_in":3600}'),
({'status': '200'}, 'echo_request_headers'),
])
http = credentials.authorize(http)

View File

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

95
tests/test_oauth2client.py Normal file → Executable file
View File

@@ -25,14 +25,18 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import base64
import datetime
import json
import mox
try:
from mox3 import mox
except ImportError:
import mox
import os
import time
import unittest
import urlparse
import six
from six.moves import urllib
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.client import AccessTokenCredentials
@@ -79,15 +83,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 = 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 = urlparse.parse_qs(expected.query)
actual_query = urlparse.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():
@@ -534,8 +538,8 @@ 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': status_code}, b''),
({'status': '200'}, json.dumps(token_response).encode('utf-8')),
({'status': '200'}, 'echo_request_headers'),
])
http = self.credentials.authorize(http)
@@ -547,8 +551,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:
@@ -571,7 +575,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')
@@ -598,9 +602,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,
@@ -609,7 +613,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))
@@ -630,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)
@@ -667,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:
@@ -690,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')
@@ -720,14 +724,15 @@ class TestAssertionCredentials(unittest.TestCase):
user_agent=user_agent)
def test_assertion_body(self):
body = urlparse.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'}, b'{"access_token":"1/3w"}'),
({'status': '200'}, 'echo_request_headers'),
])
http = self.credentials.authorize(http)
@@ -792,8 +797,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 = 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])
@@ -813,8 +818,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
)
authorize_url = flow.step1_get_authorize_url()
parsed = urlparse.urlparse(authorize_url)
q = urlparse.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])
@@ -823,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:
@@ -850,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:
@@ -864,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" }"""),
])
@@ -905,7 +910,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)
@@ -916,7 +921,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)
@@ -924,7 +929,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" }"""),
])
@@ -935,7 +940,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)
@@ -943,7 +948,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" }"""),
])
@@ -956,7 +961,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"}"""),
])
@@ -971,9 +976,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)
@@ -1003,7 +1008,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,
@@ -1013,7 +1018,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:
@@ -1027,7 +1032,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(
@@ -1038,7 +1043,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)
@@ -1050,7 +1055,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:

View File

@@ -26,13 +26,13 @@ import rsa
import time
import unittest
from http_mock import HttpMockSequence
from .http_mock import HttpMockSequence
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
@@ -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)

View File

@@ -96,7 +96,7 @@ class XsrfUtilTests(unittest.TestCase):
# Invalid with extra garbage
self.assertFalse(xsrfutil.validate_token(TEST_KEY,
token + 'x',
token + b'x',
TEST_USER_ID_1,
action_id=TEST_ACTION_ID_1,
current_time=later15mins))

53
tox.ini
View File

@@ -1,13 +1,60 @@
[tox]
envlist = py26, py27
envlist = py26openssl13, py26openssl14,
py27openssl13, py27openssl14,
py33openssl14,
py34openssl14,
pypyopenssl13, pypyopenssl14
[testenv]
deps = keyring
mox
pyopenssl
mox3
pycrypto==2.6
django>=1.5,<1.6
webtest
nose
setenv = PYTHONPATH=../google_appengine
commands = nosetests --ignore-files=test_appengine\.py
# whitelist
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:py33openssl14]
basepython = python3.3
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