Merge branch 'python3' into merge
This commit is contained in:
12
.travis.yml
12
.travis.yml
@@ -1,12 +1,16 @@
|
|||||||
language: python
|
language: python
|
||||||
python: 2.7
|
python: 2.7
|
||||||
env:
|
env:
|
||||||
- TOX_ENV=py26
|
- TOX_ENV=py26openssl13
|
||||||
- TOX_ENV=py27
|
- TOX_ENV=py26openssl14
|
||||||
- TOX_ENV=pypy
|
- TOX_ENV=py27openssl13
|
||||||
|
- TOX_ENV=py27openssl14
|
||||||
|
- TOX_ENV=py33openssl14
|
||||||
|
- TOX_ENV=py34openssl14
|
||||||
|
- TOX_ENV=pypyopenssl13
|
||||||
|
- TOX_ENV=pypyopenssl14
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
- pip install .
|
|
||||||
script:
|
script:
|
||||||
- tox -e $TOX_ENV
|
- tox -e $TOX_ENV
|
||||||
notifications:
|
notifications:
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
[](https://travis-ci.org/google/oauth2client)
|
[](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.
|
This is a client library for accessing resources protected by OAuth 2.0.
|
||||||
|
|
||||||
[Full documentation](http://google.github.io/oauth2client/)
|
[Full documentation](http://google.github.io/oauth2client/)
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib
|
import six
|
||||||
import urlparse
|
from six.moves import urllib
|
||||||
|
|
||||||
import httplib2
|
import httplib2
|
||||||
from oauth2client import clientsecrets
|
from oauth2client import clientsecrets
|
||||||
@@ -231,6 +231,9 @@ class Credentials(object):
|
|||||||
# Add in information we will need later to reconsistitue this instance.
|
# Add in information we will need later to reconsistitue this instance.
|
||||||
d['_class'] = t.__name__
|
d['_class'] = t.__name__
|
||||||
d['_module'] = t.__module__
|
d['_module'] = t.__module__
|
||||||
|
for key, val in d.items():
|
||||||
|
if isinstance(val, bytes):
|
||||||
|
d[key] = val.decode('utf-8')
|
||||||
return json.dumps(d)
|
return json.dumps(d)
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
@@ -254,6 +257,8 @@ class Credentials(object):
|
|||||||
An instance of the subclass of Credentials that was serialized with
|
An instance of the subclass of Credentials that was serialized with
|
||||||
to_json().
|
to_json().
|
||||||
"""
|
"""
|
||||||
|
if six.PY3 and isinstance(s, bytes):
|
||||||
|
s = s.decode('utf-8')
|
||||||
data = json.loads(s)
|
data = json.loads(s)
|
||||||
# Find and call the right classmethod from_json() to restore the object.
|
# Find and call the right classmethod from_json() to restore the object.
|
||||||
module = data['_module']
|
module = data['_module']
|
||||||
@@ -398,7 +403,7 @@ def clean_headers(headers):
|
|||||||
"""
|
"""
|
||||||
clean = {}
|
clean = {}
|
||||||
try:
|
try:
|
||||||
for k, v in headers.iteritems():
|
for k, v in six.iteritems(headers):
|
||||||
clean[str(k)] = str(v)
|
clean[str(k)] = str(v)
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
raise NonAsciiHeaderError(k + ': ' + v)
|
raise NonAsciiHeaderError(k + ': ' + v)
|
||||||
@@ -415,11 +420,11 @@ def _update_query_params(uri, params):
|
|||||||
Returns:
|
Returns:
|
||||||
The same URI but with the new query parameters added.
|
The same URI but with the new query parameters added.
|
||||||
"""
|
"""
|
||||||
parts = urlparse.urlparse(uri)
|
parts = urllib.parse.urlparse(uri)
|
||||||
query_params = dict(urlparse.parse_qsl(parts.query))
|
query_params = dict(urllib.parse.parse_qsl(parts.query))
|
||||||
query_params.update(params)
|
query_params.update(params)
|
||||||
new_parts = parts._replace(query=urllib.urlencode(query_params))
|
new_parts = parts._replace(query=urllib.parse.urlencode(query_params))
|
||||||
return urlparse.urlunparse(new_parts)
|
return urllib.parse.urlunparse(new_parts)
|
||||||
|
|
||||||
|
|
||||||
class OAuth2Credentials(Credentials):
|
class OAuth2Credentials(Credentials):
|
||||||
@@ -589,6 +594,8 @@ class OAuth2Credentials(Credentials):
|
|||||||
Returns:
|
Returns:
|
||||||
An instance of a Credentials subclass.
|
An instance of a Credentials subclass.
|
||||||
"""
|
"""
|
||||||
|
if six.PY3 and isinstance(s, bytes):
|
||||||
|
s = s.decode('utf-8')
|
||||||
data = json.loads(s)
|
data = json.loads(s)
|
||||||
if (data.get('token_expiry') and
|
if (data.get('token_expiry') and
|
||||||
not isinstance(data['token_expiry'], datetime.datetime)):
|
not isinstance(data['token_expiry'], datetime.datetime)):
|
||||||
@@ -691,7 +698,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
|
|
||||||
def _generate_refresh_request_body(self):
|
def _generate_refresh_request_body(self):
|
||||||
"""Generate the body that will be used in the refresh request."""
|
"""Generate the body that will be used in the refresh request."""
|
||||||
body = urllib.urlencode({
|
body = urllib.parse.urlencode({
|
||||||
'grant_type': 'refresh_token',
|
'grant_type': 'refresh_token',
|
||||||
'client_id': self.client_id,
|
'client_id': self.client_id,
|
||||||
'client_secret': self.client_secret,
|
'client_secret': self.client_secret,
|
||||||
@@ -755,8 +762,9 @@ class OAuth2Credentials(Credentials):
|
|||||||
logger.info('Refreshing access_token')
|
logger.info('Refreshing access_token')
|
||||||
resp, content = http_request(
|
resp, content = http_request(
|
||||||
self.token_uri, method='POST', body=body, headers=headers)
|
self.token_uri, method='POST', body=body, headers=headers)
|
||||||
|
if six.PY3:
|
||||||
|
content = content.decode('utf-8')
|
||||||
if resp.status == 200:
|
if resp.status == 200:
|
||||||
# TODO(jcgregorio) Raise an error if loads fails?
|
|
||||||
d = json.loads(content)
|
d = json.loads(content)
|
||||||
self.token_response = d
|
self.token_response = d
|
||||||
self.access_token = d['access_token']
|
self.access_token = d['access_token']
|
||||||
@@ -785,7 +793,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
self.invalid = True
|
self.invalid = True
|
||||||
if self.store:
|
if self.store:
|
||||||
self.store.locked_put(self)
|
self.store.locked_put(self)
|
||||||
except StandardError:
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
raise AccessTokenRefreshError(error_msg)
|
raise AccessTokenRefreshError(error_msg)
|
||||||
|
|
||||||
@@ -822,7 +830,7 @@ class OAuth2Credentials(Credentials):
|
|||||||
d = json.loads(content)
|
d = json.loads(content)
|
||||||
if 'error' in d:
|
if 'error' in d:
|
||||||
error_msg = d['error']
|
error_msg = d['error']
|
||||||
except StandardError:
|
except (TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
raise TokenRevokeError(error_msg)
|
raise TokenRevokeError(error_msg)
|
||||||
|
|
||||||
@@ -880,10 +888,12 @@ class AccessTokenCredentials(OAuth2Credentials):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, s):
|
def from_json(cls, s):
|
||||||
|
if six.PY3 and isinstance(s, bytes):
|
||||||
|
s = s.decode('utf-8')
|
||||||
data = json.loads(s)
|
data = json.loads(s)
|
||||||
retval = AccessTokenCredentials(
|
retval = AccessTokenCredentials(
|
||||||
data['access_token'],
|
data['access_token'],
|
||||||
data['user_agent'])
|
data['user_agent'])
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def _refresh(self, http_request):
|
def _refresh(self, http_request):
|
||||||
@@ -903,7 +913,7 @@ class AccessTokenCredentials(OAuth2Credentials):
|
|||||||
_env_name = None
|
_env_name = None
|
||||||
|
|
||||||
|
|
||||||
def _get_environment(urllib2_urlopen=None):
|
def _get_environment(urlopen=None):
|
||||||
"""Detect the environment the code is being run on."""
|
"""Detect the environment the code is being run on."""
|
||||||
|
|
||||||
global _env_name
|
global _env_name
|
||||||
@@ -917,16 +927,15 @@ def _get_environment(urllib2_urlopen=None):
|
|||||||
elif server_software.startswith('Development/'):
|
elif server_software.startswith('Development/'):
|
||||||
_env_name = 'GAE_LOCAL'
|
_env_name = 'GAE_LOCAL'
|
||||||
else:
|
else:
|
||||||
import urllib2
|
|
||||||
try:
|
try:
|
||||||
if urllib2_urlopen is None:
|
if urlopen is None:
|
||||||
urllib2_urlopen = urllib2.urlopen
|
urlopen = urllib.request.urlopen
|
||||||
response = urllib2_urlopen('http://metadata.google.internal')
|
response = urlopen('http://metadata.google.internal')
|
||||||
if any('Metadata-Flavor: Google' in h for h in response.info().headers):
|
if any('Metadata-Flavor: Google' in h for h in response.info().headers):
|
||||||
_env_name = 'GCE_PRODUCTION'
|
_env_name = 'GCE_PRODUCTION'
|
||||||
else:
|
else:
|
||||||
_env_name = 'UNKNOWN'
|
_env_name = 'UNKNOWN'
|
||||||
except urllib2.URLError:
|
except urllib.error.URLError:
|
||||||
_env_name = 'UNKNOWN'
|
_env_name = 'UNKNOWN'
|
||||||
|
|
||||||
return _env_name
|
return _env_name
|
||||||
@@ -956,7 +965,7 @@ class GoogleCredentials(OAuth2Credentials):
|
|||||||
request = service.instances().list(project=PROJECT, zone=ZONE)
|
request = service.instances().list(project=PROJECT, zone=ZONE)
|
||||||
response = request.execute()
|
response = request.execute()
|
||||||
|
|
||||||
print response
|
print(response)
|
||||||
</code>
|
</code>
|
||||||
|
|
||||||
A service that does not require authentication does not need credentials
|
A service that does not require authentication does not need credentials
|
||||||
@@ -970,7 +979,7 @@ class GoogleCredentials(OAuth2Credentials):
|
|||||||
request = service.apis().list()
|
request = service.apis().list()
|
||||||
response = request.execute()
|
response = request.execute()
|
||||||
|
|
||||||
print response
|
print(response)
|
||||||
</code>
|
</code>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1168,7 +1177,7 @@ def _get_application_default_credential_from_file(
|
|||||||
application_default_credential_filename):
|
application_default_credential_filename):
|
||||||
"""Build the Application Default Credentials from file."""
|
"""Build the Application Default Credentials from file."""
|
||||||
|
|
||||||
import service_account
|
from oauth2client import service_account
|
||||||
|
|
||||||
# read the credentials from the file
|
# read the credentials from the file
|
||||||
with open(application_default_credential_filename) as (
|
with open(application_default_credential_filename) as (
|
||||||
@@ -1274,7 +1283,7 @@ class AssertionCredentials(GoogleCredentials):
|
|||||||
def _generate_refresh_request_body(self):
|
def _generate_refresh_request_body(self):
|
||||||
assertion = self._generate_assertion()
|
assertion = self._generate_assertion()
|
||||||
|
|
||||||
body = urllib.urlencode({
|
body = urllib.parse.urlencode({
|
||||||
'assertion': assertion,
|
'assertion': assertion,
|
||||||
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
'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.
|
# Keep base64 encoded so it can be stored in JSON.
|
||||||
self.private_key = base64.b64encode(private_key)
|
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.private_key_password = private_key_password
|
||||||
self.service_account_name = service_account_name
|
self.service_account_name = service_account_name
|
||||||
@@ -1386,7 +1397,7 @@ class SignedJwtAssertionCredentials(AssertionCredentials):
|
|||||||
|
|
||||||
def _generate_assertion(self):
|
def _generate_assertion(self):
|
||||||
"""Generate the assertion that will be used in the request."""
|
"""Generate the assertion that will be used in the request."""
|
||||||
now = long(time.time())
|
now = int(time.time())
|
||||||
payload = {
|
payload = {
|
||||||
'aud': self.token_uri,
|
'aud': self.token_uri,
|
||||||
'scope': self.scope,
|
'scope': self.scope,
|
||||||
@@ -1435,7 +1446,7 @@ def verify_id_token(id_token, audience, http=None,
|
|||||||
resp, content = http.request(cert_uri)
|
resp, content = http.request(cert_uri)
|
||||||
|
|
||||||
if resp.status == 200:
|
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)
|
return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
|
||||||
else:
|
else:
|
||||||
raise VerifyJwtTokenError('Status code: %d' % resp.status)
|
raise VerifyJwtTokenError('Status code: %d' % resp.status)
|
||||||
@@ -1443,8 +1454,9 @@ def verify_id_token(id_token, audience, http=None,
|
|||||||
|
|
||||||
def _urlsafe_b64decode(b64string):
|
def _urlsafe_b64decode(b64string):
|
||||||
# Guard against unicode strings, which base64 can't handle.
|
# Guard against unicode strings, which base64 can't handle.
|
||||||
b64string = b64string.encode('ascii')
|
if isinstance(b64string, six.text_type):
|
||||||
padded = b64string + '=' * (4 - len(b64string) % 4)
|
b64string = b64string.encode('ascii')
|
||||||
|
padded = b64string + b'=' * (4 - len(b64string) % 4)
|
||||||
return base64.urlsafe_b64decode(padded)
|
return base64.urlsafe_b64decode(padded)
|
||||||
|
|
||||||
|
|
||||||
@@ -1465,7 +1477,7 @@ def _extract_id_token(id_token):
|
|||||||
raise VerifyJwtTokenError(
|
raise VerifyJwtTokenError(
|
||||||
'Wrong number of segments in token: %s' % id_token)
|
'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):
|
def _parse_exchange_token_response(content):
|
||||||
@@ -1483,11 +1495,11 @@ def _parse_exchange_token_response(content):
|
|||||||
"""
|
"""
|
||||||
resp = {}
|
resp = {}
|
||||||
try:
|
try:
|
||||||
resp = json.loads(content)
|
resp = json.loads(content.decode('utf-8'))
|
||||||
except StandardError:
|
except Exception:
|
||||||
# different JSON libs raise different exceptions,
|
# different JSON libs raise different exceptions,
|
||||||
# so we just do a catch-all here
|
# 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'
|
# some providers respond with 'expires', others with 'expires_in'
|
||||||
if resp and 'expires' in resp:
|
if resp and 'expires' in resp:
|
||||||
@@ -1810,7 +1822,7 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
else:
|
else:
|
||||||
post_data['grant_type'] = 'authorization_code'
|
post_data['grant_type'] = 'authorization_code'
|
||||||
post_data['redirect_uri'] = self.redirect_uri
|
post_data['redirect_uri'] = self.redirect_uri
|
||||||
body = urllib.urlencode(post_data)
|
body = urllib.parse.urlencode(post_data)
|
||||||
headers = {
|
headers = {
|
||||||
'content-type': 'application/x-www-form-urlencoded',
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
}
|
}
|
||||||
@@ -1851,7 +1863,7 @@ class OAuth2WebServerFlow(Flow):
|
|||||||
logger.info('Failed to retrieve access token: %s', content)
|
logger.info('Failed to retrieve access token: %s', content)
|
||||||
if 'error' in d:
|
if 'error' in d:
|
||||||
# you never know what those providers got to say
|
# you never know what those providers got to say
|
||||||
error_msg = unicode(d['error'])
|
error_msg = str(d['error'])
|
||||||
else:
|
else:
|
||||||
error_msg = 'Invalid response: %s.' % str(resp.status)
|
error_msg = 'Invalid response: %s.' % str(resp.status)
|
||||||
raise FlowExchangeError(error_msg)
|
raise FlowExchangeError(error_msg)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ an OAuth 2.0 protected service.
|
|||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
# Properties that make a client_secrets.json file valid.
|
# Properties that make a client_secrets.json file valid.
|
||||||
@@ -70,9 +71,9 @@ class InvalidClientSecretsError(Error):
|
|||||||
def _validate_clientsecrets(obj):
|
def _validate_clientsecrets(obj):
|
||||||
if obj is None or len(obj) != 1:
|
if obj is None or len(obj) != 1:
|
||||||
raise InvalidClientSecretsError('Invalid file format.')
|
raise InvalidClientSecretsError('Invalid file format.')
|
||||||
client_type = obj.keys()[0]
|
client_type = tuple(obj)[0]
|
||||||
if client_type not in VALID_CLIENT.keys():
|
if client_type not in VALID_CLIENT:
|
||||||
raise InvalidClientSecretsError('Unknown client type: %s.' % client_type)
|
raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type,))
|
||||||
client_info = obj[client_type]
|
client_info = obj[client_type]
|
||||||
for prop_name in VALID_CLIENT[client_type]['required']:
|
for prop_name in VALID_CLIENT[client_type]['required']:
|
||||||
if prop_name not in client_info:
|
if prop_name not in client_info:
|
||||||
@@ -98,11 +99,8 @@ def loads(s):
|
|||||||
|
|
||||||
def _loadfile(filename):
|
def _loadfile(filename):
|
||||||
try:
|
try:
|
||||||
fp = file(filename, 'r')
|
with open(filename, 'r') as fp:
|
||||||
try:
|
|
||||||
obj = json.load(fp)
|
obj = json.load(fp)
|
||||||
finally:
|
|
||||||
fp.close()
|
|
||||||
except IOError:
|
except IOError:
|
||||||
raise InvalidClientSecretsError('File not found: "%s"' % filename)
|
raise InvalidClientSecretsError('File not found: "%s"' % filename)
|
||||||
return _validate_clientsecrets(obj)
|
return _validate_clientsecrets(obj)
|
||||||
@@ -150,4 +148,4 @@ def loadfile(filename, cache=None):
|
|||||||
obj = {client_type: client_info}
|
obj = {client_type: client_info}
|
||||||
cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
|
cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
|
||||||
|
|
||||||
return obj.iteritems().next()
|
return next(six.iteritems(obj))
|
||||||
|
|||||||
@@ -18,8 +18,11 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
|
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
|
||||||
AUTH_TOKEN_LIFETIME_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.
|
key that this object was constructed with.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
if isinstance(message, six.text_type):
|
||||||
|
message = message.encode('utf-8')
|
||||||
crypto.verify(self._pubkey, signature, message, 'sha256')
|
crypto.verify(self._pubkey, signature, message, 'sha256')
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
@@ -101,15 +106,17 @@ try:
|
|||||||
"""Signs a message.
|
"""Signs a message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message: string, Message to be signed.
|
message: bytes, Message to be signed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string, The signature of the message for the given key.
|
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')
|
return crypto.sign(self._key, message, 'sha256')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_string(key, password='notasecret'):
|
def from_string(key, password=b'notasecret'):
|
||||||
"""Construct a Signer instance from a string.
|
"""Construct a Signer instance from a string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -126,7 +133,9 @@ try:
|
|||||||
if parsed_pem_key:
|
if parsed_pem_key:
|
||||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
|
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
|
||||||
else:
|
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)
|
return OpenSSLSigner(pkey)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -182,8 +191,10 @@ try:
|
|||||||
Verifier instance.
|
Verifier instance.
|
||||||
"""
|
"""
|
||||||
if is_x509_cert:
|
if is_x509_cert:
|
||||||
pemLines = key_pem.replace(' ', '').split()
|
if isinstance(key_pem, six.text_type):
|
||||||
certDer = _urlsafe_b64decode(''.join(pemLines[1:-1]))
|
key_pem = key_pem.encode('ascii')
|
||||||
|
pemLines = key_pem.replace(b' ', b'').split()
|
||||||
|
certDer = _urlsafe_b64decode(b''.join(pemLines[1:-1]))
|
||||||
certSeq = DerSequence()
|
certSeq = DerSequence()
|
||||||
certSeq.decode(certDer)
|
certSeq.decode(certDer)
|
||||||
tbsSeq = DerSequence()
|
tbsSeq = DerSequence()
|
||||||
@@ -214,6 +225,8 @@ try:
|
|||||||
Returns:
|
Returns:
|
||||||
string, The signature of the message for the given key.
|
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))
|
return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -269,19 +282,22 @@ def _parse_pem_key(raw_key_input):
|
|||||||
Returns:
|
Returns:
|
||||||
string, The actual key if the contents are from a PEM file, or else None.
|
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:
|
if offset != -1:
|
||||||
return raw_key_input[offset:]
|
return raw_key_input[offset:]
|
||||||
|
|
||||||
|
|
||||||
def _urlsafe_b64encode(raw_bytes):
|
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):
|
def _urlsafe_b64decode(b64string):
|
||||||
# Guard against unicode strings, which base64 can't handle.
|
# Guard against unicode strings, which base64 can't handle.
|
||||||
b64string = b64string.encode('ascii')
|
if isinstance(b64string, six.text_type):
|
||||||
padded = b64string + '=' * (4 - len(b64string) % 4)
|
b64string = b64string.encode('ascii')
|
||||||
|
padded = b64string + b'=' * (4 - len(b64string) % 4)
|
||||||
return base64.urlsafe_b64decode(padded)
|
return base64.urlsafe_b64decode(padded)
|
||||||
|
|
||||||
|
|
||||||
@@ -345,13 +361,13 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
|
|||||||
# Parse token.
|
# Parse token.
|
||||||
json_body = _urlsafe_b64decode(segments[1])
|
json_body = _urlsafe_b64decode(segments[1])
|
||||||
try:
|
try:
|
||||||
parsed = json.loads(json_body)
|
parsed = json.loads(json_body.decode('utf-8'))
|
||||||
except:
|
except:
|
||||||
raise AppIdentityError('Can\'t parse token: %s' % json_body)
|
raise AppIdentityError('Can\'t parse token: %s' % json_body)
|
||||||
|
|
||||||
# Check signature.
|
# Check signature.
|
||||||
verified = False
|
verified = False
|
||||||
for _, pem in certs.items():
|
for pem in certs.values():
|
||||||
verifier = Verifier.from_string(pem, True)
|
verifier = Verifier.from_string(pem, True)
|
||||||
if verifier.verify(signed, signature):
|
if verifier.verify(signed, signature):
|
||||||
verified = True
|
verified = True
|
||||||
@@ -366,7 +382,7 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
|
|||||||
earliest = iat - CLOCK_SKEW_SECS
|
earliest = iat - CLOCK_SKEW_SECS
|
||||||
|
|
||||||
# Check expiration timestamp.
|
# Check expiration timestamp.
|
||||||
now = long(time.time())
|
now = int(time.time())
|
||||||
exp = parsed.get('exp')
|
exp = parsed.get('exp')
|
||||||
if exp is None:
|
if exp is None:
|
||||||
raise AppIdentityError('No exp field in token: %s' % json_body)
|
raise AppIdentityError('No exp field in token: %s' % json_body)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class Storage(BaseStorage):
|
|||||||
simple version of "touch" to ensure the file has been created.
|
simple version of "touch" to ensure the file has been created.
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(self._filename):
|
if not os.path.exists(self._filename):
|
||||||
old_umask = os.umask(0177)
|
old_umask = os.umask(0o177)
|
||||||
try:
|
try:
|
||||||
open(self._filename, 'a+b').close()
|
open(self._filename, 'a+b').close()
|
||||||
finally:
|
finally:
|
||||||
@@ -108,7 +108,7 @@ class Storage(BaseStorage):
|
|||||||
|
|
||||||
self._create_file_if_needed()
|
self._create_file_if_needed()
|
||||||
self._validate_file()
|
self._validate_file()
|
||||||
f = open(self._filename, 'wb')
|
f = open(self._filename, 'w')
|
||||||
f.write(credentials.to_json())
|
f.write(credentials.to_json())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
from oauth2client import util
|
from oauth2client import util
|
||||||
from oauth2client.client import AccessTokenRefreshError
|
from oauth2client.client import AccessTokenRefreshError
|
||||||
@@ -78,13 +78,13 @@ class AppAssertionCredentials(AssertionCredentials):
|
|||||||
Raises:
|
Raises:
|
||||||
AccessTokenRefreshError: When the refresh fails.
|
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)
|
uri = META.replace('{?scope}', query)
|
||||||
response, content = http_request(uri)
|
response, content = http_request(uri)
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
try:
|
try:
|
||||||
d = json.loads(content)
|
d = json.loads(content)
|
||||||
except StandardError as e:
|
except Exception as e:
|
||||||
raise AccessTokenRefreshError(str(e))
|
raise AccessTokenRefreshError(str(e))
|
||||||
self.access_token = d['accessToken']
|
self.access_token = d['accessToken']
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ Usage:
|
|||||||
f = LockedFile('filename', 'r+b', 'rb')
|
f = LockedFile('filename', 'r+b', 'rb')
|
||||||
f.open_and_lock()
|
f.open_and_lock()
|
||||||
if f.is_locked():
|
if f.is_locked():
|
||||||
print 'Acquired filename with r+b mode'
|
print('Acquired filename with r+b mode')
|
||||||
f.file_handle().write('locked data')
|
f.file_handle().write('locked data')
|
||||||
else:
|
else:
|
||||||
print 'Aquired filename with rb mode'
|
print('Aquired filename with rb mode')
|
||||||
f.unlock_and_close()
|
f.unlock_and_close()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class _MultiStore(object):
|
|||||||
|
|
||||||
This will create the file if necessary.
|
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._thread_lock = threading.Lock()
|
||||||
self._read_only = False
|
self._read_only = False
|
||||||
self._warn_on_readonly = warn_on_readonly
|
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.
|
simple version of "touch" to ensure the file has been created.
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(self._file.filename()):
|
if not os.path.exists(self._file.filename()):
|
||||||
old_umask = os.umask(0177)
|
old_umask = os.umask(0o177)
|
||||||
try:
|
try:
|
||||||
open(self._file.filename(), 'a+b').close()
|
open(self._file.filename(), 'a+b').close()
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import gflags
|
|||||||
|
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import util
|
from oauth2client import util
|
||||||
from tools import ClientRedirectHandler
|
from oauth2client.tools import ClientRedirectHandler
|
||||||
from tools import ClientRedirectServer
|
from oauth2client.tools import ClientRedirectServer
|
||||||
|
|
||||||
|
|
||||||
FLAGS = gflags.FLAGS
|
FLAGS = gflags.FLAGS
|
||||||
@@ -103,13 +103,13 @@ def run(flow, storage, http=None):
|
|||||||
break
|
break
|
||||||
FLAGS.auth_local_webserver = success
|
FLAGS.auth_local_webserver = success
|
||||||
if not success:
|
if not success:
|
||||||
print 'Failed to start a local webserver listening on either port 8080'
|
print('Failed to start a local webserver listening on either port 8080')
|
||||||
print 'or port 9090. Please check your firewall settings and locally'
|
print('or port 9090. Please check your firewall settings and locally')
|
||||||
print 'running programs that may be blocking or using those ports.'
|
print('running programs that may be blocking or using those ports.')
|
||||||
print
|
print()
|
||||||
print 'Falling back to --noauth_local_webserver and continuing with',
|
print('Falling back to --noauth_local_webserver and continuing with')
|
||||||
print 'authorization.'
|
print('authorization.')
|
||||||
print
|
print()
|
||||||
|
|
||||||
if FLAGS.auth_local_webserver:
|
if FLAGS.auth_local_webserver:
|
||||||
oauth_callback = 'http://%s:%s/' % (FLAGS.auth_host_name, port_number)
|
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:
|
if FLAGS.auth_local_webserver:
|
||||||
webbrowser.open(authorize_url, new=1, autoraise=True)
|
webbrowser.open(authorize_url, new=1, autoraise=True)
|
||||||
print 'Your browser has been opened to visit:'
|
print('Your browser has been opened to visit:')
|
||||||
print
|
print()
|
||||||
print ' ' + authorize_url
|
print(' ' + authorize_url)
|
||||||
print
|
print()
|
||||||
print 'If your browser is on a different machine then exit and re-run'
|
print('If your browser is on a different machine then exit and re-run')
|
||||||
print 'this application with the command-line parameter '
|
print('this application with the command-line parameter ')
|
||||||
print
|
print()
|
||||||
print ' --noauth_local_webserver'
|
print(' --noauth_local_webserver')
|
||||||
print
|
print()
|
||||||
else:
|
else:
|
||||||
print 'Go to the following link in your browser:'
|
print('Go to the following link in your browser:')
|
||||||
print
|
print()
|
||||||
print ' ' + authorize_url
|
print(' ' + authorize_url)
|
||||||
print
|
print()
|
||||||
|
|
||||||
code = None
|
code = None
|
||||||
if FLAGS.auth_local_webserver:
|
if FLAGS.auth_local_webserver:
|
||||||
@@ -143,7 +143,7 @@ def run(flow, storage, http=None):
|
|||||||
if 'code' in httpd.query_params:
|
if 'code' in httpd.query_params:
|
||||||
code = httpd.query_params['code']
|
code = httpd.query_params['code']
|
||||||
else:
|
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.')
|
sys.exit('Try running with --noauth_local_webserver.')
|
||||||
else:
|
else:
|
||||||
code = raw_input('Enter verification code: ').strip()
|
code = raw_input('Enter verification code: ').strip()
|
||||||
@@ -155,6 +155,6 @@ def run(flow, storage, http=None):
|
|||||||
|
|
||||||
storage.put(credential)
|
storage.put(credential)
|
||||||
credential.set_store(storage)
|
credential.set_store(storage)
|
||||||
print 'Authentication successful.'
|
print('Authentication successful.')
|
||||||
|
|
||||||
return credential
|
return credential
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
|||||||
'kid': self._private_key_id
|
'kid': self._private_key_id
|
||||||
}
|
}
|
||||||
|
|
||||||
now = long(time.time())
|
now = int(time.time())
|
||||||
payload = {
|
payload = {
|
||||||
'aud': self._token_uri,
|
'aud': self._token_uri,
|
||||||
'scope': self._scopes,
|
'scope': self._scopes,
|
||||||
@@ -77,14 +77,20 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
|||||||
assertion_input = '%s.%s' % (
|
assertion_input = '%s.%s' % (
|
||||||
_urlsafe_b64encode(header),
|
_urlsafe_b64encode(header),
|
||||||
_urlsafe_b64encode(payload))
|
_urlsafe_b64encode(payload))
|
||||||
|
assertion_input = assertion_input.encode('utf-8')
|
||||||
|
|
||||||
# Sign the assertion.
|
# Sign the assertion.
|
||||||
signature = base64.urlsafe_b64encode(rsa.pkcs1.sign(
|
signature = bytes.decode(base64.urlsafe_b64encode(rsa.pkcs1.sign(
|
||||||
assertion_input, self._private_key, 'SHA-256')).rstrip('=')
|
assertion_input, self._private_key, 'SHA-256'))).rstrip('=')
|
||||||
|
|
||||||
return '%s.%s' % (assertion_input, signature)
|
return '%s.%s' % (assertion_input, signature)
|
||||||
|
|
||||||
def sign_blob(self, blob):
|
def sign_blob(self, blob):
|
||||||
|
# Ensure that it is bytes
|
||||||
|
try:
|
||||||
|
blob = blob.encode('utf-8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
return (self._private_key_id,
|
return (self._private_key_id,
|
||||||
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
|
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
|
||||||
|
|
||||||
@@ -119,7 +125,7 @@ class _ServiceAccountCredentials(AssertionCredentials):
|
|||||||
|
|
||||||
def _urlsafe_b64encode(data):
|
def _urlsafe_b64encode(data):
|
||||||
return base64.urlsafe_b64encode(
|
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):
|
def _get_private_key(private_key_pkcs8_text):
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ import BaseHTTPServer
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import urlparse
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
|
from six.moves import urllib
|
||||||
|
|
||||||
from oauth2client import client
|
from oauth2client import client
|
||||||
from oauth2client import util
|
from oauth2client import util
|
||||||
|
|
||||||
|
|
||||||
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
|
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
|
||||||
|
|
||||||
To make this sample run you will need to populate the client_secrets.json file
|
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.send_header("Content-type", "text/html")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
query = self.path.split('?', 1)[-1]
|
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.server.query_params = query
|
||||||
self.wfile.write("<html><head><title>Authentication Status</title></head>")
|
self.wfile.write("<html><head><title>Authentication Status</title></head>")
|
||||||
self.wfile.write("<body><p>The authentication flow has completed.</p>")
|
self.wfile.write("<body><p>The authentication flow has completed.</p>")
|
||||||
@@ -155,20 +157,20 @@ def run_flow(flow, storage, flags, http=None):
|
|||||||
try:
|
try:
|
||||||
httpd = ClientRedirectServer((flags.auth_host_name, port),
|
httpd = ClientRedirectServer((flags.auth_host_name, port),
|
||||||
ClientRedirectHandler)
|
ClientRedirectHandler)
|
||||||
except socket.error as e:
|
except socket.error:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
success = True
|
success = True
|
||||||
break
|
break
|
||||||
flags.noauth_local_webserver = not success
|
flags.noauth_local_webserver = not success
|
||||||
if not success:
|
if not success:
|
||||||
print 'Failed to start a local webserver listening on either port 8080'
|
print('Failed to start a local webserver listening on either port 8080')
|
||||||
print 'or port 9090. Please check your firewall settings and locally'
|
print('or port 9090. Please check your firewall settings and locally')
|
||||||
print 'running programs that may be blocking or using those ports.'
|
print('running programs that may be blocking or using those ports.')
|
||||||
print
|
print()
|
||||||
print 'Falling back to --noauth_local_webserver and continuing with',
|
print('Falling back to --noauth_local_webserver and continuing with')
|
||||||
print 'authorization.'
|
print('authorization.')
|
||||||
print
|
print()
|
||||||
|
|
||||||
if not flags.noauth_local_webserver:
|
if not flags.noauth_local_webserver:
|
||||||
oauth_callback = 'http://%s:%s/' % (flags.auth_host_name, port_number)
|
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:
|
if not flags.noauth_local_webserver:
|
||||||
webbrowser.open(authorize_url, new=1, autoraise=True)
|
webbrowser.open(authorize_url, new=1, autoraise=True)
|
||||||
print 'Your browser has been opened to visit:'
|
print('Your browser has been opened to visit:')
|
||||||
print
|
print()
|
||||||
print ' ' + authorize_url
|
print(' ' + authorize_url)
|
||||||
print
|
print()
|
||||||
print 'If your browser is on a different machine then exit and re-run this'
|
print('If your browser is on a different machine then exit and re-run this')
|
||||||
print 'application with the command-line parameter '
|
print('application with the command-line parameter ')
|
||||||
print
|
print()
|
||||||
print ' --noauth_local_webserver'
|
print(' --noauth_local_webserver')
|
||||||
print
|
print()
|
||||||
else:
|
else:
|
||||||
print 'Go to the following link in your browser:'
|
print('Go to the following link in your browser:')
|
||||||
print
|
print()
|
||||||
print ' ' + authorize_url
|
print(' ' + authorize_url)
|
||||||
print
|
print()
|
||||||
|
|
||||||
code = None
|
code = None
|
||||||
if not flags.noauth_local_webserver:
|
if not flags.noauth_local_webserver:
|
||||||
@@ -202,7 +204,7 @@ def run_flow(flow, storage, flags, http=None):
|
|||||||
if 'code' in httpd.query_params:
|
if 'code' in httpd.query_params:
|
||||||
code = httpd.query_params['code']
|
code = httpd.query_params['code']
|
||||||
else:
|
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.')
|
sys.exit('Try running with --noauth_local_webserver.')
|
||||||
else:
|
else:
|
||||||
code = raw_input('Enter verification code: ').strip()
|
code = raw_input('Enter verification code: ').strip()
|
||||||
@@ -214,7 +216,7 @@ def run_flow(flow, storage, flags, http=None):
|
|||||||
|
|
||||||
storage.put(credential)
|
storage.put(credential)
|
||||||
credential.set_store(storage)
|
credential.set_store(storage)
|
||||||
print 'Authentication successful.'
|
print('Authentication successful.')
|
||||||
|
|
||||||
return credential
|
return credential
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,12 @@ __all__ = [
|
|||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import types
|
import types
|
||||||
import urllib
|
|
||||||
import urlparse
|
import six
|
||||||
|
from six.moves import urllib
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -129,7 +132,7 @@ def positional(max_positional_args):
|
|||||||
return wrapped(*args, **kwargs)
|
return wrapped(*args, **kwargs)
|
||||||
return positional_wrapper
|
return positional_wrapper
|
||||||
|
|
||||||
if isinstance(max_positional_args, (int, long)):
|
if isinstance(max_positional_args, six.integer_types):
|
||||||
return positional_decorator
|
return positional_decorator
|
||||||
else:
|
else:
|
||||||
args, _, _, defaults = inspect.getargspec(max_positional_args)
|
args, _, _, defaults = inspect.getargspec(max_positional_args)
|
||||||
@@ -149,7 +152,11 @@ def scopes_to_string(scopes):
|
|||||||
Returns:
|
Returns:
|
||||||
The scopes formatted as a single string.
|
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
|
return scopes
|
||||||
else:
|
else:
|
||||||
return ' '.join(scopes)
|
return ' '.join(scopes)
|
||||||
@@ -186,8 +193,8 @@ def _add_query_parameter(url, name, value):
|
|||||||
if value is None:
|
if value is None:
|
||||||
return url
|
return url
|
||||||
else:
|
else:
|
||||||
parsed = list(urlparse.urlparse(url))
|
parsed = list(urllib.parse.urlparse(url))
|
||||||
q = dict(urlparse.parse_qsl(parsed[4]))
|
q = dict(urllib.parse.parse_qsl(parsed[4]))
|
||||||
q[name] = value
|
q[name] = value
|
||||||
parsed[4] = urllib.urlencode(q)
|
parsed[4] = urllib.parse.urlencode(q)
|
||||||
return urlparse.urlunparse(parsed)
|
return urllib.parse.urlunparse(parsed)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/python2.5
|
|
||||||
#
|
#
|
||||||
# Copyright 2014 the Melange authors.
|
# Copyright 2014 the Melange authors.
|
||||||
#
|
#
|
||||||
@@ -26,15 +25,27 @@ import base64
|
|||||||
import hmac
|
import hmac
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import six
|
||||||
from oauth2client import util
|
from oauth2client import util
|
||||||
|
|
||||||
|
|
||||||
# Delimiter character
|
# Delimiter character
|
||||||
DELIMITER = ':'
|
DELIMITER = b':'
|
||||||
|
|
||||||
|
|
||||||
# 1 hour in seconds
|
# 1 hour in seconds
|
||||||
DEFAULT_TIMEOUT_SECS = 1*60*60
|
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)
|
@util.positional(2)
|
||||||
def generate_token(key, user_id, action_id="", when=None):
|
def generate_token(key, user_id, action_id="", when=None):
|
||||||
"""Generates a URL-safe token for the given user, action, time tuple.
|
"""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:
|
Returns:
|
||||||
A string XSRF protection token.
|
A string XSRF protection token.
|
||||||
"""
|
"""
|
||||||
when = when or int(time.time())
|
when = _force_bytes(when or int(time.time()))
|
||||||
digester = hmac.new(key)
|
digester = hmac.new(_force_bytes(key))
|
||||||
digester.update(str(user_id))
|
digester.update(_force_bytes(user_id))
|
||||||
digester.update(DELIMITER)
|
digester.update(DELIMITER)
|
||||||
digester.update(action_id)
|
digester.update(_force_bytes(action_id))
|
||||||
digester.update(DELIMITER)
|
digester.update(DELIMITER)
|
||||||
digester.update(str(when))
|
digester.update(when)
|
||||||
digest = digester.digest()
|
digest = digester.digest()
|
||||||
|
|
||||||
token = base64.urlsafe_b64encode('%s%s%d' % (digest,
|
token = base64.urlsafe_b64encode(digest + DELIMITER + when)
|
||||||
DELIMITER,
|
|
||||||
when))
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
@@ -86,8 +95,8 @@ def validate_token(key, token, user_id, action_id="", current_time=None):
|
|||||||
if not token:
|
if not token:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
decoded = base64.urlsafe_b64decode(str(token))
|
decoded = base64.urlsafe_b64decode(token)
|
||||||
token_time = long(decoded.split(DELIMITER)[-1])
|
token_time = int(decoded.split(DELIMITER)[-1])
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return False
|
return False
|
||||||
if current_time is None:
|
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
|
# Perform constant time comparison to avoid timing attacks
|
||||||
different = 0
|
different = 0
|
||||||
for x, y in zip(token, expected_token):
|
for x, y in zip(bytearray(token), bytearray(expected_token)):
|
||||||
different |= ord(x) ^ ord(y)
|
different |= x ^ y
|
||||||
if different:
|
return different == 0
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ class HttpMockSequence(object):
|
|||||||
and content and then use as if an httplib2.Http instance.
|
and content and then use as if an httplib2.Http instance.
|
||||||
|
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '401'}, ''),
|
({'status': '401'}, b''),
|
||||||
({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
|
({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
|
||||||
({'status': '200'}, 'echo_request_headers'),
|
({'status': '200'}, 'echo_request_headers'),
|
||||||
])
|
])
|
||||||
resp, content = http.request("http://examples.com")
|
resp, content = http.request("http://examples.com")
|
||||||
@@ -111,4 +111,6 @@ class HttpMockSequence(object):
|
|||||||
content = body
|
content = body
|
||||||
elif content == 'echo_request_uri':
|
elif content == 'echo_request_uri':
|
||||||
content = uri
|
content = uri
|
||||||
|
elif not isinstance(content, bytes):
|
||||||
|
raise TypeError("http content should be bytes: %r" % (content,))
|
||||||
return httplib2.Response(resp), content
|
return httplib2.Response(resp), content
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ from google.appengine.ext import db
|
|||||||
from google.appengine.ext import ndb
|
from google.appengine.ext import ndb
|
||||||
from google.appengine.ext import testbed
|
from google.appengine.ext import testbed
|
||||||
from google.appengine.runtime import apiproxy_errors
|
from google.appengine.runtime import apiproxy_errors
|
||||||
from http_mock import HttpMockSequence
|
|
||||||
from oauth2client import appengine
|
from oauth2client import appengine
|
||||||
from oauth2client import GOOGLE_TOKEN_URI
|
from oauth2client import GOOGLE_TOKEN_URI
|
||||||
from oauth2client.clientsecrets import _loadfile
|
from oauth2client.clientsecrets import _loadfile
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
import httplib2
|
import httplib2
|
||||||
|
|
||||||
@@ -57,6 +57,11 @@ class OAuth2CredentialsTests(unittest.TestCase):
|
|||||||
""", 'Property'),
|
""", 'Property'),
|
||||||
]
|
]
|
||||||
for src, match in ERRORS:
|
for src, match in ERRORS:
|
||||||
|
# Ensure that it is unicode
|
||||||
|
try:
|
||||||
|
src = src.decode('utf-8')
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
# Test load(s)
|
# Test load(s)
|
||||||
try:
|
try:
|
||||||
clientsecrets.loads(src)
|
clientsecrets.loads(src)
|
||||||
@@ -66,7 +71,7 @@ class OAuth2CredentialsTests(unittest.TestCase):
|
|||||||
|
|
||||||
# Test loads(fp)
|
# Test loads(fp)
|
||||||
try:
|
try:
|
||||||
fp = StringIO.StringIO(src)
|
fp = StringIO(src)
|
||||||
clientsecrets.load(fp)
|
clientsecrets.load(fp)
|
||||||
self.fail(src + ' should not be a valid client_secrets file.')
|
self.fail(src + ' should not be a valid client_secrets file.')
|
||||||
except clientsecrets.InvalidClientSecretsError as e:
|
except clientsecrets.InvalidClientSecretsError as e:
|
||||||
@@ -104,25 +109,25 @@ class CachedClientsecretsTests(unittest.TestCase):
|
|||||||
def test_cache_miss(self):
|
def test_cache_miss(self):
|
||||||
client_type, client_info = clientsecrets.loadfile(
|
client_type, client_info = clientsecrets.loadfile(
|
||||||
VALID_FILE, cache=self.cache_mock)
|
VALID_FILE, cache=self.cache_mock)
|
||||||
self.assertEquals('web', client_type)
|
self.assertEqual('web', client_type)
|
||||||
self.assertEquals('foo_client_secret', client_info['client_secret'])
|
self.assertEqual('foo_client_secret', client_info['client_secret'])
|
||||||
|
|
||||||
cached = self.cache_mock.cache[VALID_FILE]
|
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
|
# make sure we're using non-empty namespace
|
||||||
ns = self.cache_mock.last_set_ns
|
ns = self.cache_mock.last_set_ns
|
||||||
self.assertTrue(bool(ns))
|
self.assertTrue(bool(ns))
|
||||||
# make sure they're equal
|
# 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):
|
def test_cache_hit(self):
|
||||||
self.cache_mock.cache[NONEXISTENT_FILE] = { 'web': 'secret info' }
|
self.cache_mock.cache[NONEXISTENT_FILE] = { 'web': 'secret info' }
|
||||||
|
|
||||||
client_type, client_info = clientsecrets.loadfile(
|
client_type, client_info = clientsecrets.loadfile(
|
||||||
NONEXISTENT_FILE, cache=self.cache_mock)
|
NONEXISTENT_FILE, cache=self.cache_mock)
|
||||||
self.assertEquals('web', client_type)
|
self.assertEqual('web', client_type)
|
||||||
self.assertEquals('secret info', client_info)
|
self.assertEqual('secret info', client_info)
|
||||||
# make sure we didn't do any set() RPCs
|
# make sure we didn't do any set() RPCs
|
||||||
self.assertEqual(None, self.cache_mock.last_set_ns)
|
self.assertEqual(None, self.cache_mock.last_set_ns)
|
||||||
|
|
||||||
@@ -137,8 +142,8 @@ class CachedClientsecretsTests(unittest.TestCase):
|
|||||||
def test_without_cache(self):
|
def test_without_cache(self):
|
||||||
# this also ensures loadfile() is backward compatible
|
# this also ensures loadfile() is backward compatible
|
||||||
client_type, client_info = clientsecrets.loadfile(VALID_FILE)
|
client_type, client_info = clientsecrets.loadfile(VALID_FILE)
|
||||||
self.assertEquals('web', client_type)
|
self.assertEqual('web', client_type)
|
||||||
self.assertEquals('foo_client_secret', client_info['client_secret'])
|
self.assertEqual('foo_client_secret', client_info['client_secret'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ from oauth2client.client import Credentials
|
|||||||
from oauth2client.client import Flow
|
from oauth2client.client import Flow
|
||||||
|
|
||||||
# Mock a Django environment
|
# Mock a Django environment
|
||||||
|
from django.conf import global_settings
|
||||||
|
global_settings.SECRET_KEY = 'NotASecret'
|
||||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_settings'
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_settings'
|
||||||
sys.modules['django_settings'] = django_settings = imp.new_module('django_settings')
|
sys.modules['django_settings'] = django_settings = imp.new_module('django_settings')
|
||||||
django_settings.SECRET_KEY = 'xyzzy'
|
django_settings.SECRET_KEY = 'xyzzy'
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import stat
|
|||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from http_mock import HttpMockSequence
|
|
||||||
from oauth2client import GOOGLE_TOKEN_URI
|
from oauth2client import GOOGLE_TOKEN_URI
|
||||||
from oauth2client import file
|
from oauth2client import file
|
||||||
from oauth2client import locked_file
|
from oauth2client import locked_file
|
||||||
@@ -41,6 +40,11 @@ from oauth2client import util
|
|||||||
from oauth2client.client import AccessTokenCredentials
|
from oauth2client.client import AccessTokenCredentials
|
||||||
from oauth2client.client import AssertionCredentials
|
from oauth2client.client import AssertionCredentials
|
||||||
from oauth2client.client import OAuth2Credentials
|
from oauth2client.client import OAuth2Credentials
|
||||||
|
try:
|
||||||
|
# Python2
|
||||||
|
from future_builtins import oct
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
FILENAME = tempfile.mktemp('oauth2client_test.data')
|
FILENAME = tempfile.mktemp('oauth2client_test.data')
|
||||||
@@ -96,7 +100,7 @@ class OAuth2ClientFileTests(unittest.TestCase):
|
|||||||
# Write a file with a pickled OAuth2Credentials.
|
# Write a file with a pickled OAuth2Credentials.
|
||||||
credentials = self.create_test_credentials()
|
credentials = self.create_test_credentials()
|
||||||
|
|
||||||
f = open(FILENAME, 'w')
|
f = open(FILENAME, 'wb')
|
||||||
pickle.dump(credentials, f)
|
pickle.dump(credentials, f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
@@ -154,13 +158,13 @@ class OAuth2ClientFileTests(unittest.TestCase):
|
|||||||
mode = os.stat(FILENAME).st_mode
|
mode = os.stat(FILENAME).st_mode
|
||||||
|
|
||||||
if os.name == 'posix':
|
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):
|
def test_read_only_file_fail_lock(self):
|
||||||
credentials = self.create_test_credentials()
|
credentials = self.create_test_credentials()
|
||||||
|
|
||||||
open(FILENAME, 'a+b').close()
|
open(FILENAME, 'a+b').close()
|
||||||
os.chmod(FILENAME, 0400)
|
os.chmod(FILENAME, 0o400)
|
||||||
|
|
||||||
store = multistore_file.get_credential_storage(
|
store = multistore_file.get_credential_storage(
|
||||||
FILENAME,
|
FILENAME,
|
||||||
@@ -171,7 +175,7 @@ class OAuth2ClientFileTests(unittest.TestCase):
|
|||||||
store.put(credentials)
|
store.put(credentials)
|
||||||
if os.name == 'posix':
|
if os.name == 'posix':
|
||||||
self.assertTrue(store._multistore._read_only)
|
self.assertTrue(store._multistore._read_only)
|
||||||
os.chmod(FILENAME, 0600)
|
os.chmod(FILENAME, 0o600)
|
||||||
|
|
||||||
def test_multistore_no_symbolic_link_files(self):
|
def test_multistore_no_symbolic_link_files(self):
|
||||||
if hasattr(os, 'symlink'):
|
if hasattr(os, 'symlink'):
|
||||||
@@ -221,7 +225,7 @@ class OAuth2ClientFileTests(unittest.TestCase):
|
|||||||
self.assertEquals(None, credentials)
|
self.assertEquals(None, credentials)
|
||||||
|
|
||||||
if os.name == 'posix':
|
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):
|
def test_multistore_file_custom_key(self):
|
||||||
credentials = self.create_test_credentials()
|
credentials = self.create_test_credentials()
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ Unit tests for oauth2client.gce.
|
|||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||||
|
|
||||||
import httplib2
|
import httplib2
|
||||||
import mox
|
try:
|
||||||
|
from mox3 import mox
|
||||||
|
except ImportError:
|
||||||
|
import mox
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from oauth2client.client import AccessTokenRefreshError
|
from oauth2client.client import AccessTokenRefreshError
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ Unit tests for oauth2client.
|
|||||||
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from http_mock import HttpMockSequence
|
from .http_mock import HttpMockSequence
|
||||||
from oauth2client import crypt
|
from oauth2client import crypt
|
||||||
from oauth2client.client import Credentials
|
from oauth2client.client import Credentials
|
||||||
from oauth2client.client import SignedJwtAssertionCredentials
|
from oauth2client.client import SignedJwtAssertionCredentials
|
||||||
@@ -39,7 +40,7 @@ from oauth2client.file import Storage
|
|||||||
|
|
||||||
|
|
||||||
def datafile(filename):
|
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()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return data
|
return data
|
||||||
@@ -67,11 +68,10 @@ class CryptTests(unittest.TestCase):
|
|||||||
signature = signer.sign('foo')
|
signature = signer.sign('foo')
|
||||||
|
|
||||||
verifier = self.verifier.from_string(public_key, True)
|
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(b'bar', signature))
|
||||||
|
self.assertFalse(verifier.verify(b'foo', 'bad signagure'))
|
||||||
self.assertFalse(verifier.verify('bar', signature))
|
|
||||||
self.assertFalse(verifier.verify('foo', 'bad signature'))
|
|
||||||
|
|
||||||
def _check_jwt_failure(self, jwt, expected_error):
|
def _check_jwt_failure(self, jwt, expected_error):
|
||||||
public_key = datafile('publickey.pem')
|
public_key = datafile('publickey.pem')
|
||||||
@@ -88,7 +88,7 @@ class CryptTests(unittest.TestCase):
|
|||||||
private_key = datafile('privatekey.%s' % self.format)
|
private_key = datafile('privatekey.%s' % self.format)
|
||||||
signer = self.signer.from_string(private_key)
|
signer = self.signer.from_string(private_key)
|
||||||
audience = 'some_audience_address@testing.gserviceaccount.com'
|
audience = 'some_audience_address@testing.gserviceaccount.com'
|
||||||
now = long(time.time())
|
now = int(time.time())
|
||||||
|
|
||||||
return crypt.make_signed_jwt(signer, {
|
return crypt.make_signed_jwt(signer, {
|
||||||
'aud': audience,
|
'aud': audience,
|
||||||
@@ -212,11 +212,11 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase):
|
|||||||
scope='read+write',
|
scope='read+write',
|
||||||
sub='joe@example.org')
|
sub='joe@example.org')
|
||||||
http = HttpMockSequence([
|
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'),
|
({'status': '200'}, 'echo_request_headers'),
|
||||||
])
|
])
|
||||||
http = credentials.authorize(http)
|
http = credentials.authorize(http)
|
||||||
_, content = http.request('http://example.org')
|
resp, content = http.request('http://example.org')
|
||||||
self.assertEqual('Bearer 1/3w', content['Authorization'])
|
self.assertEqual('Bearer 1/3w', content['Authorization'])
|
||||||
|
|
||||||
def test_credentials_to_from_json(self):
|
def test_credentials_to_from_json(self):
|
||||||
@@ -235,9 +235,9 @@ class SignedJwtAssertionCredentialsTests(unittest.TestCase):
|
|||||||
|
|
||||||
def _credentials_refresh(self, credentials):
|
def _credentials_refresh(self, credentials):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
|
({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
|
||||||
({'status': '401'}, ''),
|
({'status': '401'}, b''),
|
||||||
({'status': '200'}, '{"access_token":"3/3w","expires_in":3600}'),
|
({'status': '200'}, b'{"access_token":"3/3w","expires_in":3600}'),
|
||||||
({'status': '200'}, 'echo_request_headers'),
|
({'status': '200'}, 'echo_request_headers'),
|
||||||
])
|
])
|
||||||
http = credentials.authorize(http)
|
http = credentials.authorize(http)
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|||||||
import datetime
|
import datetime
|
||||||
import keyring
|
import keyring
|
||||||
import unittest
|
import unittest
|
||||||
import mox
|
try:
|
||||||
|
from mox3 import mox
|
||||||
|
except ImportError:
|
||||||
|
import mox
|
||||||
|
|
||||||
from oauth2client import GOOGLE_TOKEN_URI
|
from oauth2client import GOOGLE_TOKEN_URI
|
||||||
from oauth2client.client import OAuth2Credentials
|
from oauth2client.client import OAuth2Credentials
|
||||||
|
|||||||
95
tests/test_oauth2client.py
Normal file → Executable file
95
tests/test_oauth2client.py
Normal file → Executable file
@@ -25,14 +25,18 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
|
|||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import mox
|
try:
|
||||||
|
from mox3 import mox
|
||||||
|
except ImportError:
|
||||||
|
import mox
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
import urlparse
|
import six
|
||||||
|
from six.moves import urllib
|
||||||
|
|
||||||
from http_mock import HttpMock
|
from .http_mock import HttpMock
|
||||||
from http_mock import HttpMockSequence
|
from .http_mock import HttpMockSequence
|
||||||
from oauth2client import GOOGLE_REVOKE_URI
|
from oauth2client import GOOGLE_REVOKE_URI
|
||||||
from oauth2client import GOOGLE_TOKEN_URI
|
from oauth2client import GOOGLE_TOKEN_URI
|
||||||
from oauth2client.client import AccessTokenCredentials
|
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.
|
# googleapiclient.test_discovery; consolidate these definitions.
|
||||||
def assertUrisEqual(testcase, expected, actual):
|
def assertUrisEqual(testcase, expected, actual):
|
||||||
"""Test that URIs are the same, up to reordering of query parameters."""
|
"""Test that URIs are the same, up to reordering of query parameters."""
|
||||||
expected = urlparse.urlparse(expected)
|
expected = urllib.parse.urlparse(expected)
|
||||||
actual = urlparse.urlparse(actual)
|
actual = urllib.parse.urlparse(actual)
|
||||||
testcase.assertEqual(expected.scheme, actual.scheme)
|
testcase.assertEqual(expected.scheme, actual.scheme)
|
||||||
testcase.assertEqual(expected.netloc, actual.netloc)
|
testcase.assertEqual(expected.netloc, actual.netloc)
|
||||||
testcase.assertEqual(expected.path, actual.path)
|
testcase.assertEqual(expected.path, actual.path)
|
||||||
testcase.assertEqual(expected.params, actual.params)
|
testcase.assertEqual(expected.params, actual.params)
|
||||||
testcase.assertEqual(expected.fragment, actual.fragment)
|
testcase.assertEqual(expected.fragment, actual.fragment)
|
||||||
expected_query = urlparse.parse_qs(expected.query)
|
expected_query = urllib.parse.parse_qs(expected.query)
|
||||||
actual_query = urlparse.parse_qs(actual.query)
|
actual_query = urllib.parse.parse_qs(actual.query)
|
||||||
for name in expected_query.keys():
|
for name in expected_query.keys():
|
||||||
testcase.assertEqual(expected_query[name], actual_query[name])
|
testcase.assertEqual(expected_query[name], actual_query[name])
|
||||||
for name in actual_query.keys():
|
for name in actual_query.keys():
|
||||||
@@ -534,8 +538,8 @@ class BasicCredentialsTests(unittest.TestCase):
|
|||||||
for status_code in REFRESH_STATUS_CODES:
|
for status_code in REFRESH_STATUS_CODES:
|
||||||
token_response = {'access_token': '1/3w', 'expires_in': 3600}
|
token_response = {'access_token': '1/3w', 'expires_in': 3600}
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': status_code}, ''),
|
({'status': status_code}, b''),
|
||||||
({'status': '200'}, json.dumps(token_response)),
|
({'status': '200'}, json.dumps(token_response).encode('utf-8')),
|
||||||
({'status': '200'}, 'echo_request_headers'),
|
({'status': '200'}, 'echo_request_headers'),
|
||||||
])
|
])
|
||||||
http = self.credentials.authorize(http)
|
http = self.credentials.authorize(http)
|
||||||
@@ -547,8 +551,8 @@ class BasicCredentialsTests(unittest.TestCase):
|
|||||||
def test_token_refresh_failure(self):
|
def test_token_refresh_failure(self):
|
||||||
for status_code in REFRESH_STATUS_CODES:
|
for status_code in REFRESH_STATUS_CODES:
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': status_code}, ''),
|
({'status': status_code}, b''),
|
||||||
({'status': '400'}, '{"error":"access_denied"}'),
|
({'status': '400'}, b'{"error":"access_denied"}'),
|
||||||
])
|
])
|
||||||
http = self.credentials.authorize(http)
|
http = self.credentials.authorize(http)
|
||||||
try:
|
try:
|
||||||
@@ -571,7 +575,7 @@ class BasicCredentialsTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_non_401_error_response(self):
|
def test_non_401_error_response(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '400'}, ''),
|
({'status': '400'}, b''),
|
||||||
])
|
])
|
||||||
http = self.credentials.authorize(http)
|
http = self.credentials.authorize(http)
|
||||||
resp, content = http.request('http://example.com')
|
resp, content = http.request('http://example.com')
|
||||||
@@ -598,9 +602,9 @@ class BasicCredentialsTests(unittest.TestCase):
|
|||||||
client_id = u'some_client_id'
|
client_id = u'some_client_id'
|
||||||
client_secret = u'cOuDdkfjxxnv+'
|
client_secret = u'cOuDdkfjxxnv+'
|
||||||
refresh_token = u'1/0/a.df219fjls0'
|
refresh_token = u'1/0/a.df219fjls0'
|
||||||
token_expiry = unicode(datetime.datetime.utcnow())
|
token_expiry = str(datetime.datetime.utcnow())
|
||||||
token_uri = unicode(GOOGLE_TOKEN_URI)
|
token_uri = str(GOOGLE_TOKEN_URI)
|
||||||
revoke_uri = unicode(GOOGLE_REVOKE_URI)
|
revoke_uri = str(GOOGLE_REVOKE_URI)
|
||||||
user_agent = u'refresh_checker/1.0'
|
user_agent = u'refresh_checker/1.0'
|
||||||
credentials = OAuth2Credentials(access_token, client_id, client_secret,
|
credentials = OAuth2Credentials(access_token, client_id, client_secret,
|
||||||
refresh_token, token_expiry, token_uri,
|
refresh_token, token_expiry, token_uri,
|
||||||
@@ -609,7 +613,7 @@ class BasicCredentialsTests(unittest.TestCase):
|
|||||||
http = HttpMock(headers={'status': '200'})
|
http = HttpMock(headers={'status': '200'})
|
||||||
http = credentials.authorize(http)
|
http = credentials.authorize(http)
|
||||||
http.request(u'http://example.com', method=u'GET', headers={u'foo': u'bar'})
|
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(k))
|
||||||
self.assertEqual(str, type(v))
|
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_first = {'access_token': 'first_token', 'expires_in': S}
|
||||||
token_response_second = {'access_token': 'second_token', 'expires_in': S}
|
token_response_second = {'access_token': 'second_token', 'expires_in': S}
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, json.dumps(token_response_first)),
|
({'status': '200'}, json.dumps(token_response_first).encode('utf-8')),
|
||||||
({'status': '200'}, json.dumps(token_response_second)),
|
({'status': '200'}, json.dumps(token_response_second).encode('utf-8')),
|
||||||
])
|
])
|
||||||
|
|
||||||
token = self.credentials.get_access_token(http=http)
|
token = self.credentials.get_access_token(http=http)
|
||||||
@@ -667,7 +671,7 @@ class AccessTokenCredentialsTests(unittest.TestCase):
|
|||||||
def test_token_refresh_success(self):
|
def test_token_refresh_success(self):
|
||||||
for status_code in REFRESH_STATUS_CODES:
|
for status_code in REFRESH_STATUS_CODES:
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': status_code}, ''),
|
({'status': status_code}, b''),
|
||||||
])
|
])
|
||||||
http = self.credentials.authorize(http)
|
http = self.credentials.authorize(http)
|
||||||
try:
|
try:
|
||||||
@@ -690,7 +694,7 @@ class AccessTokenCredentialsTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_non_401_error_response(self):
|
def test_non_401_error_response(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '400'}, ''),
|
({'status': '400'}, b''),
|
||||||
])
|
])
|
||||||
http = self.credentials.authorize(http)
|
http = self.credentials.authorize(http)
|
||||||
resp, content = http.request('http://example.com')
|
resp, content = http.request('http://example.com')
|
||||||
@@ -720,14 +724,15 @@ class TestAssertionCredentials(unittest.TestCase):
|
|||||||
user_agent=user_agent)
|
user_agent=user_agent)
|
||||||
|
|
||||||
def test_assertion_body(self):
|
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(self.assertion_text, body['assertion'][0])
|
||||||
self.assertEqual('urn:ietf:params:oauth:grant-type:jwt-bearer',
|
self.assertEqual('urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
body['grant_type'][0])
|
body['grant_type'][0])
|
||||||
|
|
||||||
def test_assertion_refresh(self):
|
def test_assertion_refresh(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, '{"access_token":"1/3w"}'),
|
({'status': '200'}, b'{"access_token":"1/3w"}'),
|
||||||
({'status': '200'}, 'echo_request_headers'),
|
({'status': '200'}, 'echo_request_headers'),
|
||||||
])
|
])
|
||||||
http = self.credentials.authorize(http)
|
http = self.credentials.authorize(http)
|
||||||
@@ -792,8 +797,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
def test_construct_authorize_url(self):
|
def test_construct_authorize_url(self):
|
||||||
authorize_url = self.flow.step1_get_authorize_url()
|
authorize_url = self.flow.step1_get_authorize_url()
|
||||||
|
|
||||||
parsed = urlparse.urlparse(authorize_url)
|
parsed = urllib.parse.urlparse(authorize_url)
|
||||||
q = urlparse.parse_qs(parsed[4])
|
q = urllib.parse.parse_qs(parsed[4])
|
||||||
self.assertEqual('client_id+1', q['client_id'][0])
|
self.assertEqual('client_id+1', q['client_id'][0])
|
||||||
self.assertEqual('code', q['response_type'][0])
|
self.assertEqual('code', q['response_type'][0])
|
||||||
self.assertEqual('foo', q['scope'][0])
|
self.assertEqual('foo', q['scope'][0])
|
||||||
@@ -813,8 +818,8 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
authorize_url = flow.step1_get_authorize_url()
|
authorize_url = flow.step1_get_authorize_url()
|
||||||
|
|
||||||
parsed = urlparse.urlparse(authorize_url)
|
parsed = urllib.parse.urlparse(authorize_url)
|
||||||
q = urlparse.parse_qs(parsed[4])
|
q = urllib.parse.parse_qs(parsed[4])
|
||||||
self.assertEqual('client_id+1', q['client_id'][0])
|
self.assertEqual('client_id+1', q['client_id'][0])
|
||||||
self.assertEqual('token', q['response_type'][0])
|
self.assertEqual('token', q['response_type'][0])
|
||||||
self.assertEqual('foo', q['scope'][0])
|
self.assertEqual('foo', q['scope'][0])
|
||||||
@@ -823,7 +828,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_exchange_failure(self):
|
def test_exchange_failure(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '400'}, '{"error":"invalid_request"}'),
|
({'status': '400'}, b'{"error":"invalid_request"}'),
|
||||||
])
|
])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -850,9 +855,9 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
# exceptions are being raised instead of FlowExchangeError.
|
# exceptions are being raised instead of FlowExchangeError.
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '400'},
|
({'status': '400'},
|
||||||
""" {"error": {
|
b""" {"error": {
|
||||||
"type": "OAuthException",
|
"type": "OAuthException",
|
||||||
"message": "Error validating verification code."} }"""),
|
"message": "Error validating verification code."} }"""),
|
||||||
])
|
])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -864,7 +869,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
def test_exchange_success(self):
|
def test_exchange_success(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'},
|
({'status': '200'},
|
||||||
"""{ "access_token":"SlAV32hkKG",
|
b"""{ "access_token":"SlAV32hkKG",
|
||||||
"expires_in":3600,
|
"expires_in":3600,
|
||||||
"refresh_token":"8xLOxBtZp8" }"""),
|
"refresh_token":"8xLOxBtZp8" }"""),
|
||||||
])
|
])
|
||||||
@@ -905,7 +910,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_urlencoded_exchange_success(self):
|
def test_urlencoded_exchange_success(self):
|
||||||
http = HttpMockSequence([
|
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)
|
credentials = self.flow.step2_exchange('some random code', http=http)
|
||||||
@@ -916,7 +921,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
# Note the 'expires=3600' where you'd normally
|
# Note the 'expires=3600' where you'd normally
|
||||||
# have if named 'expires_in'
|
# 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)
|
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):
|
def test_exchange_no_expires_in(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, """{ "access_token":"SlAV32hkKG",
|
({'status': '200'}, b"""{ "access_token":"SlAV32hkKG",
|
||||||
"refresh_token":"8xLOxBtZp8" }"""),
|
"refresh_token":"8xLOxBtZp8" }"""),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -935,7 +940,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
# This might be redundant but just to make sure
|
# This might be redundant but just to make sure
|
||||||
# urlencoded access_token gets parsed correctly
|
# 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)
|
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):
|
def test_exchange_fails_if_no_code(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, """{ "access_token":"SlAV32hkKG",
|
({'status': '200'}, b"""{ "access_token":"SlAV32hkKG",
|
||||||
"refresh_token":"8xLOxBtZp8" }"""),
|
"refresh_token":"8xLOxBtZp8" }"""),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -956,7 +961,7 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_exchange_id_token_fail(self):
|
def test_exchange_id_token_fail(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, """{ "access_token":"SlAV32hkKG",
|
({'status': '200'}, b"""{ "access_token":"SlAV32hkKG",
|
||||||
"refresh_token":"8xLOxBtZp8",
|
"refresh_token":"8xLOxBtZp8",
|
||||||
"id_token": "stuff.payload"}"""),
|
"id_token": "stuff.payload"}"""),
|
||||||
])
|
])
|
||||||
@@ -971,9 +976,9 @@ class OAuth2WebServerFlowTest(unittest.TestCase):
|
|||||||
base64.urlsafe_b64encode('signature'))
|
base64.urlsafe_b64encode('signature'))
|
||||||
|
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, """{ "access_token":"SlAV32hkKG",
|
({'status': '200'}, ("""{ "access_token":"SlAV32hkKG",
|
||||||
"refresh_token":"8xLOxBtZp8",
|
"refresh_token":"8xLOxBtZp8",
|
||||||
"id_token": "%s"}""" % jwt),
|
"id_token": "%s"}""" % jwt).encode('utf-8')),
|
||||||
])
|
])
|
||||||
|
|
||||||
credentials = self.flow.step2_exchange('some random code', http=http)
|
credentials = self.flow.step2_exchange('some random code', http=http)
|
||||||
@@ -1003,7 +1008,7 @@ class CredentialsFromCodeTests(unittest.TestCase):
|
|||||||
token = 'asdfghjkl'
|
token = 'asdfghjkl'
|
||||||
payload = json.dumps({'access_token': token, 'expires_in': 3600})
|
payload = json.dumps({'access_token': token, 'expires_in': 3600})
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, payload),
|
({'status': '200'}, payload.encode('utf-8')),
|
||||||
])
|
])
|
||||||
credentials = credentials_from_code(self.client_id, self.client_secret,
|
credentials = credentials_from_code(self.client_id, self.client_secret,
|
||||||
self.scope, self.code, redirect_uri=self.redirect_uri,
|
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):
|
def test_exchange_code_for_token_fail(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '400'}, '{"error":"invalid_request"}'),
|
({'status': '400'}, b'{"error":"invalid_request"}'),
|
||||||
])
|
])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1027,7 +1032,7 @@ class CredentialsFromCodeTests(unittest.TestCase):
|
|||||||
def test_exchange_code_and_file_for_token(self):
|
def test_exchange_code_and_file_for_token(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'},
|
({'status': '200'},
|
||||||
"""{ "access_token":"asdfghjkl",
|
b"""{ "access_token":"asdfghjkl",
|
||||||
"expires_in":3600 }"""),
|
"expires_in":3600 }"""),
|
||||||
])
|
])
|
||||||
credentials = credentials_from_clientsecrets_and_code(
|
credentials = credentials_from_clientsecrets_and_code(
|
||||||
@@ -1038,7 +1043,7 @@ class CredentialsFromCodeTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_exchange_code_and_cached_file_for_token(self):
|
def test_exchange_code_and_cached_file_for_token(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, '{ "access_token":"asdfghjkl"}'),
|
({'status': '200'}, b'{ "access_token":"asdfghjkl"}'),
|
||||||
])
|
])
|
||||||
cache_mock = CacheMock()
|
cache_mock = CacheMock()
|
||||||
load_and_cache('client_secrets.json', 'some_secrets', cache_mock)
|
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):
|
def test_exchange_code_and_file_for_token_fail(self):
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '400'}, '{"error":"invalid_request"}'),
|
({'status': '400'}, b'{"error":"invalid_request"}'),
|
||||||
])
|
])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ import rsa
|
|||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from http_mock import HttpMockSequence
|
from .http_mock import HttpMockSequence
|
||||||
from oauth2client.service_account import _ServiceAccountCredentials
|
from oauth2client.service_account import _ServiceAccountCredentials
|
||||||
|
|
||||||
|
|
||||||
def datafile(filename):
|
def datafile(filename):
|
||||||
# TODO(orestica): Refactor this using pkgutil.get_data
|
# 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()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return data
|
return data
|
||||||
@@ -58,16 +58,16 @@ class ServiceAccountCredentialsTests(unittest.TestCase):
|
|||||||
pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(
|
pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(
|
||||||
datafile('publickey_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:
|
try:
|
||||||
rsa.pkcs1.verify('Orest', signature, pub_key)
|
rsa.pkcs1.verify(b'Orest', signature, pub_key)
|
||||||
self.fail('Verification should have failed!')
|
self.fail('Verification should have failed!')
|
||||||
except rsa.pkcs1.VerificationError:
|
except rsa.pkcs1.VerificationError:
|
||||||
pass # Expected
|
pass # Expected
|
||||||
|
|
||||||
try:
|
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!')
|
self.fail('Verification should have failed!')
|
||||||
except rsa.pkcs1.VerificationError:
|
except rsa.pkcs1.VerificationError:
|
||||||
pass # Expected
|
pass # Expected
|
||||||
@@ -98,8 +98,8 @@ class ServiceAccountCredentialsTests(unittest.TestCase):
|
|||||||
token_response_first = {'access_token': 'first_token', 'expires_in': S}
|
token_response_first = {'access_token': 'first_token', 'expires_in': S}
|
||||||
token_response_second = {'access_token': 'second_token', 'expires_in': S}
|
token_response_second = {'access_token': 'second_token', 'expires_in': S}
|
||||||
http = HttpMockSequence([
|
http = HttpMockSequence([
|
||||||
({'status': '200'}, json.dumps(token_response_first)),
|
({'status': '200'}, json.dumps(token_response_first).encode('utf-8')),
|
||||||
({'status': '200'}, json.dumps(token_response_second)),
|
({'status': '200'}, json.dumps(token_response_second).encode('utf-8')),
|
||||||
])
|
])
|
||||||
|
|
||||||
token = self.credentials.get_access_token(http=http)
|
token = self.credentials.get_access_token(http=http)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class XsrfUtilTests(unittest.TestCase):
|
|||||||
|
|
||||||
# Invalid with extra garbage
|
# Invalid with extra garbage
|
||||||
self.assertFalse(xsrfutil.validate_token(TEST_KEY,
|
self.assertFalse(xsrfutil.validate_token(TEST_KEY,
|
||||||
token + 'x',
|
token + b'x',
|
||||||
TEST_USER_ID_1,
|
TEST_USER_ID_1,
|
||||||
action_id=TEST_ACTION_ID_1,
|
action_id=TEST_ACTION_ID_1,
|
||||||
current_time=later15mins))
|
current_time=later15mins))
|
||||||
|
|||||||
53
tox.ini
53
tox.ini
@@ -1,13 +1,60 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py26, py27
|
envlist = py26openssl13, py26openssl14,
|
||||||
|
py27openssl13, py27openssl14,
|
||||||
|
py33openssl14,
|
||||||
|
py34openssl14,
|
||||||
|
pypyopenssl13, pypyopenssl14
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = keyring
|
deps = keyring
|
||||||
mox
|
mox3
|
||||||
pyopenssl
|
|
||||||
pycrypto==2.6
|
pycrypto==2.6
|
||||||
django>=1.5,<1.6
|
django>=1.5,<1.6
|
||||||
webtest
|
webtest
|
||||||
nose
|
nose
|
||||||
setenv = PYTHONPATH=../google_appengine
|
setenv = PYTHONPATH=../google_appengine
|
||||||
commands = nosetests --ignore-files=test_appengine\.py
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user