From 2864ebc7a52832f0fda3820d6aeb8723c0d09597 Mon Sep 17 00:00:00 2001 From: Craig Citro Date: Fri, 9 Jan 2015 13:35:51 -0800 Subject: [PATCH 1/7] Bump GCE detection timeout to 1 sec. --- oauth2client/client.py | 2 +- tests/test_oauth2client.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/oauth2client/client.py b/oauth2client/client.py index 5432c82092..9ebbcbf344 100644 --- a/oauth2client/client.py +++ b/oauth2client/client.py @@ -938,7 +938,7 @@ def _detect_gce_environment(urlopen=None): # the metadata resolution was particularly slow. The latter case is # "unlikely". try: - response = urlopen('http://169.254.169.254/', timeout=0.1) + response = urlopen('http://169.254.169.254/', timeout=1) return response.info().get('Metadata-Flavor', '') == 'Google' except socket.timeout: logger.info('Timeout attempting to reach GCE metadata service.') diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py index aa3f068839..fb44de4775 100644 --- a/tests/test_oauth2client.py +++ b/tests/test_oauth2client.py @@ -215,7 +215,7 @@ def test_get_environment_gce_production(self): autospec=True) as urlopen: self.assertEqual('GCE_PRODUCTION', _get_environment()) urlopen.assert_called_once_with( - 'http://169.254.169.254/', timeout=0.1) + 'http://169.254.169.254/', timeout=1) def test_get_environment_unknown(self): os.environ['SERVER_SOFTWARE'] = '' @@ -224,7 +224,7 @@ def test_get_environment_unknown(self): autospec=True) as urlopen: self.assertEqual(DEFAULT_ENV_NAME, _get_environment()) urlopen.assert_called_once_with( - 'http://169.254.169.254/', timeout=0.1) + 'http://169.254.169.254/', timeout=1) def test_get_environment_variable_file(self): environment_variable_file = datafile( From bb2e7708ab80d13faf7b36a1f05999ce8496e6ef Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Wed, 14 Jan 2015 17:02:21 -0800 Subject: [PATCH 2/7] Adding protected method to convert PKCS12 key to PEM. --- oauth2client/crypt.py | 34 +++++++++++++++++++++++++++- tests/test_jwt.py | 52 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index 1c5748e1cf..f877de12e0 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -137,7 +137,6 @@ def from_string(key, password=b'notasecret'): password = password.encode('utf-8') pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) - except ImportError: OpenSSLVerifier = None OpenSSLSigner = None @@ -287,6 +286,39 @@ def _parse_pem_key(raw_key_input): return raw_key_input[offset:] +def private_key_as_pem(private_key_text, private_key_password=None): + """Convert the contents of a key to PEM. + + First tries to determine if the current key is PEM, then tries to + use OpenSSL to convert from PKCS12 to PEM. + + Args: + private_key_text: String. Private key. + private_key_password: Optional string. Password for PKCS12. + + Returns: + String. PEM contents of ``private_key_text``. + + Raises: + ImportError: If key is PKCS12 and OpenSSL is not installed. + """ + decoded_body = base64.b64decode(private_key_text) + pem_contents = _parse_pem_key(decoded_body) + if pem_contents is None: + if OpenSSLVerifier is None or OpenSSLSigner is None: + raise ImportError('OpenSSL not installed. Required to convert ' + 'PKCS12 key to PEM.') + + if isinstance(private_key_password, six.string_types): + private_key_password = private_key_password.encode('ascii') + + pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password) + pem_contents = crypto.dump_privatekey(crypto.FILETYPE_PEM, + pkcs12.get_privatekey()) + + return pem_contents + + def _urlsafe_b64encode(raw_bytes): if isinstance(raw_bytes, six.text_type): raw_bytes = raw_bytes.encode('utf-8') diff --git a/tests/test_jwt.py b/tests/test_jwt.py index a2cd37d356..7d09b6d36e 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -23,19 +23,21 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)' import os +import mock import sys import tempfile import time import unittest from .http_mock import HttpMockSequence -from oauth2client import crypt +from oauth2client import client from oauth2client.client import Credentials from oauth2client.client import SignedJwtAssertionCredentials from oauth2client.client import VerifyJwtTokenError from oauth2client.client import verify_id_token from oauth2client.client import HAS_OPENSSL from oauth2client.client import HAS_CRYPTO +from oauth2client import crypt from oauth2client.file import Storage @@ -47,6 +49,7 @@ def datafile(filename): class CryptTests(unittest.TestCase): + def setUp(self): self.format = 'p12' self.signer = crypt.OpenSSLSigner @@ -185,6 +188,51 @@ def test_verify_id_token_bad_tokens(self): self._check_jwt_failure(jwt, 'Wrong recipient') +class Test_crypt_private_key_as_pem(unittest.TestCase): + + def _make_signed_jwt_creds(self, private_key_file='privatekey.p12', + private_key=None): + private_key = private_key or datafile(private_key_file) + return SignedJwtAssertionCredentials( + 'some_account@example.com', + private_key, + scope='read+write', + sub='joe@example.org') + + def test_succeeds(self): + self.assertEqual(True, HAS_OPENSSL) + + credentials = self._make_signed_jwt_creds() + pem_contents = crypt.private_key_as_pem( + credentials.private_key, + private_key_password=credentials.private_key_password) + + private_key_as_pem = datafile('pem_from_pkcs12.pem') + private_key_as_pem = crypt._parse_pem_key(private_key_as_pem) + self.assertEqual(pem_contents, private_key_as_pem) + + def test_without_openssl(self): + credentials = self._make_signed_jwt_creds() + with mock.patch('oauth2client.crypt.OpenSSLSigner', None): + self.assertRaises(ImportError, crypt.private_key_as_pem, + credentials.private_key, + private_key_password=credentials.private_key_password) + + def test_with_pem_key(self): + credentials = self._make_signed_jwt_creds(private_key_file='privatekey.pem') + pem_contents = crypt.private_key_as_pem( + credentials.private_key, + private_key_password=credentials.private_key_password) + expected_pem_key = datafile('privatekey.pem') + self.assertEqual(pem_contents, expected_pem_key) + + def test_with_nonsense_key(self): + credentials = self._make_signed_jwt_creds(private_key=b'NOT_A_KEY') + self.assertRaises(crypt.crypto.Error, crypt.private_key_as_pem, + credentials.private_key, + private_key_password=credentials.private_key_password) + + class PEMCryptTestsPyCrypto(CryptTests): def setUp(self): self.format = 'pem' @@ -291,6 +339,7 @@ def setUp(self): class PKCSSignedJwtAssertionCredentialsPyCryptoTests(unittest.TestCase): + def test_for_failure(self): crypt.Signer = crypt.PyCryptoSigner private_key = datafile('privatekey.p12') @@ -311,5 +360,6 @@ def test_true(self): self.assertEqual(True, HAS_OPENSSL) self.assertEqual(True, HAS_CRYPTO) + if __name__ == '__main__': unittest.main() From 4d020992b36b4afd753cc5e69646c70d1ac2a7c2 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Wed, 14 Jan 2015 20:24:45 -0800 Subject: [PATCH 3/7] Moving private_key_as_pem->pkcs12_key_as_pem. Also only defining if OpenSSL is installed and conditionally defining a method which raises NotImplementedError if not defined. --- oauth2client/crypt.py | 54 +++++++++++++------------------- tests/test_crypt.py | 73 +++++++++++++++++++++++++++++++++++++++++++ tests/test_jwt.py | 45 -------------------------- 3 files changed, 94 insertions(+), 78 deletions(-) create mode 100644 tests/test_crypt.py diff --git a/oauth2client/crypt.py b/oauth2client/crypt.py index f877de12e0..381f389e4c 100644 --- a/oauth2client/crypt.py +++ b/oauth2client/crypt.py @@ -137,9 +137,30 @@ def from_string(key, password=b'notasecret'): password = password.encode('utf-8') pkey = crypto.load_pkcs12(key, password).get_privatekey() return OpenSSLSigner(pkey) + + + def pkcs12_key_as_pem(private_key_text, private_key_password): + """Convert the contents of a PKCS12 key to PEM using OpenSSL. + + Args: + private_key_text: String. Private key. + private_key_password: String. Password for PKCS12. + + Returns: + String. PEM contents of ``private_key_text``. + """ + decoded_body = base64.b64decode(private_key_text) + if isinstance(private_key_password, six.string_types): + private_key_password = private_key_password.encode('ascii') + + pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password) + return crypto.dump_privatekey(crypto.FILETYPE_PEM, + pkcs12.get_privatekey()) except ImportError: OpenSSLVerifier = None OpenSSLSigner = None + def pkcs12_key_as_pem(*args, **kwargs): + raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.') try: @@ -286,39 +307,6 @@ def _parse_pem_key(raw_key_input): return raw_key_input[offset:] -def private_key_as_pem(private_key_text, private_key_password=None): - """Convert the contents of a key to PEM. - - First tries to determine if the current key is PEM, then tries to - use OpenSSL to convert from PKCS12 to PEM. - - Args: - private_key_text: String. Private key. - private_key_password: Optional string. Password for PKCS12. - - Returns: - String. PEM contents of ``private_key_text``. - - Raises: - ImportError: If key is PKCS12 and OpenSSL is not installed. - """ - decoded_body = base64.b64decode(private_key_text) - pem_contents = _parse_pem_key(decoded_body) - if pem_contents is None: - if OpenSSLVerifier is None or OpenSSLSigner is None: - raise ImportError('OpenSSL not installed. Required to convert ' - 'PKCS12 key to PEM.') - - if isinstance(private_key_password, six.string_types): - private_key_password = private_key_password.encode('ascii') - - pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password) - pem_contents = crypto.dump_privatekey(crypto.FILETYPE_PEM, - pkcs12.get_privatekey()) - - return pem_contents - - def _urlsafe_b64encode(raw_bytes): if isinstance(raw_bytes, six.text_type): raw_bytes = raw_bytes.encode('utf-8') diff --git a/tests/test_crypt.py b/tests/test_crypt.py new file mode 100644 index 0000000000..f437148a34 --- /dev/null +++ b/tests/test_crypt.py @@ -0,0 +1,73 @@ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock +import os +import sys +import unittest + +try: + reload +except NameError: + # For Python3 (though importlib should be used, silly 3.3). + from imp import reload + +from oauth2client.client import HAS_OPENSSL +from oauth2client.client import SignedJwtAssertionCredentials +from oauth2client import crypt + + +def datafile(filename): + f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'rb') + data = f.read() + f.close() + return data + + +class Test_pkcs12_key_as_pem(unittest.TestCase): + + def _make_signed_jwt_creds(self, private_key_file='privatekey.p12', + private_key=None): + private_key = private_key or datafile(private_key_file) + return SignedJwtAssertionCredentials( + 'some_account@example.com', + private_key, + scope='read+write', + sub='joe@example.org') + + def test_succeeds(self): + self.assertEqual(True, HAS_OPENSSL) + + credentials = self._make_signed_jwt_creds() + pem_contents = crypt.pkcs12_key_as_pem(credentials.private_key, + credentials.private_key_password) + pkcs12_key_as_pem = datafile('pem_from_pkcs12.pem') + pkcs12_key_as_pem = crypt._parse_pem_key(pkcs12_key_as_pem) + self.assertEqual(pem_contents, pkcs12_key_as_pem) + + def test_without_openssl(self): + openssl_mod = sys.modules['OpenSSL'] + try: + sys.modules['OpenSSL'] = None + reload(crypt) + self.assertRaises(NotImplementedError, crypt.pkcs12_key_as_pem, + 'FOO', 'BAR') + finally: + sys.modules['OpenSSL'] = openssl_mod + reload(crypt) + + def test_with_nonsense_key(self): + credentials = self._make_signed_jwt_creds(private_key=b'NOT_A_KEY') + self.assertRaises(crypt.crypto.Error, crypt.pkcs12_key_as_pem, + credentials.private_key, credentials.private_key_password) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 7d09b6d36e..f58a294782 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -188,51 +188,6 @@ def test_verify_id_token_bad_tokens(self): self._check_jwt_failure(jwt, 'Wrong recipient') -class Test_crypt_private_key_as_pem(unittest.TestCase): - - def _make_signed_jwt_creds(self, private_key_file='privatekey.p12', - private_key=None): - private_key = private_key or datafile(private_key_file) - return SignedJwtAssertionCredentials( - 'some_account@example.com', - private_key, - scope='read+write', - sub='joe@example.org') - - def test_succeeds(self): - self.assertEqual(True, HAS_OPENSSL) - - credentials = self._make_signed_jwt_creds() - pem_contents = crypt.private_key_as_pem( - credentials.private_key, - private_key_password=credentials.private_key_password) - - private_key_as_pem = datafile('pem_from_pkcs12.pem') - private_key_as_pem = crypt._parse_pem_key(private_key_as_pem) - self.assertEqual(pem_contents, private_key_as_pem) - - def test_without_openssl(self): - credentials = self._make_signed_jwt_creds() - with mock.patch('oauth2client.crypt.OpenSSLSigner', None): - self.assertRaises(ImportError, crypt.private_key_as_pem, - credentials.private_key, - private_key_password=credentials.private_key_password) - - def test_with_pem_key(self): - credentials = self._make_signed_jwt_creds(private_key_file='privatekey.pem') - pem_contents = crypt.private_key_as_pem( - credentials.private_key, - private_key_password=credentials.private_key_password) - expected_pem_key = datafile('privatekey.pem') - self.assertEqual(pem_contents, expected_pem_key) - - def test_with_nonsense_key(self): - credentials = self._make_signed_jwt_creds(private_key=b'NOT_A_KEY') - self.assertRaises(crypt.crypto.Error, crypt.private_key_as_pem, - credentials.private_key, - private_key_password=credentials.private_key_password) - - class PEMCryptTestsPyCrypto(CryptTests): def setUp(self): self.format = 'pem' From 7f19c406099d3a38496a2f9f04dfdb052096e23e Mon Sep 17 00:00:00 2001 From: Craig Citro Date: Thu, 15 Jan 2015 13:22:01 -0800 Subject: [PATCH 4/7] Update changelog to markdown. [skip ci] --- CHANGELOG | 223 --------------------------------------------------- CHANGELOG.md | 216 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 223 deletions(-) delete mode 100644 CHANGELOG create mode 100644 CHANGELOG.md diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index c4fc46cb9a..0000000000 --- a/CHANGELOG +++ /dev/null @@ -1,223 +0,0 @@ -v1.4.5 - Version 1.4.5 - - Set a shorter timeout for an Application Default Credentials issue on some - networks. (#93, #101) - Test cleanup, switch from mox to mock. (#103) - Switch docs to sphinx from epydoc. - -v1.4.4 - Version 1.4.4 - - Fix a bug in bytes/string encoding of headers. - -v1.4.3 - Version 1.4.3 - - Big thanks to @dhermes for spotting and fixing a mess in our test setup. - - Fix a serious issue with tests not being run. (#86, #87, #89) - Start credentials cleanup for single 2LO/3LO call. (#83, #84) - Clean up stack traces when re-raising in some places. (#79) - Clean up doc building. (#81, #82) - Fixed minimum version for `six` dependency. (#75) - -v1.4.2 - Version 1.4.2 - - Several small bugfixes related to six/py3 support. - -v1.4.1 - Version 1.4.1 - - Fix a critical bug on import in oauth2client.tools. - -v1.4 - Version 1.4 - - Merge python3 branch! Massive thanks due to @pferate and @methane for doing - the heavy lifting. - - Make oauth2client.tools import gracefully if argparse isn't present. - - Change `flow.step2_exchange` to preserve the raw `id_token` in the - `token_response` field. - -v1.3.2 - Version 1.3.2 - - Quick bugfix for an issue with dict-like arguments to `flow.step2_exchange`, - which is common in some environments (such as GAE). - -v1.3.1 - Version 1.3.1 - - Quick bugfix for bad error handling in from_json. - -v1.3 - Version 1.3 - - Added support for the Google Application Default Credentials; see - https://developers.google.com/accounts/docs/application-default-credentials - for more information (thanks @orestica). - Added support for OAuth2 for devices (#3, thanks @sde-melo). - The minimum required Python version is now 2.6. - The `anyjson` submodule has been removed. - - - Better exception handling around missing crypto libraries (#56). - - Improve error messages in `AccessTokenRefreshError` (#53, thanks - @erickoledadevrel). - - Drop uritemplate as a dependency. - - Handle X509 certs with PyCrypto (#51, thanks @liujin-google). - - Handle additional failure types on OSX (#32, thanks @simoncadman). - - Better unicode handling with PKCS12 passwords (#31, thanks @jterrace). - - Better retry handling with bad server replies on refresh (#29, thanks - @kaste). - - Better logging for missing `refresh_token` in server replies (#21). - - Support `login_hint` (#18, thanks @jay0lee). - - Better overwrite options in `django_orm.Storage`. (#2, thanks - @lraccomando). - - -v1.2 - Version 1.2 - - The use of the gflags library is now deprecated, and is no longer a - dependency. If you are still using the oauth2client.tools.run() function - then include gflags as a dependency of your application or switch to - oauth2client.tools.run_flow. - Samples have been updated to use the new apiclient.sample_tools, and no - longer use gflags. - Added support for the experimental Object Change Notification, as found in - the Cloud Storage API. - The oauth2client App Engine decorators are now threadsafe. - - - Use the following redirects feature of httplib2 where it returns the - ultimate URL after a series of redirects to avoid multiple hops for every - resumable media upload request. - - Updated AdSense Management API samples to V1.3 - - Add option to automatically retry requests. - - Ability to list registered keys in multistore_file. - - User-agent must contain (gzip). - - The 'method' parameter for httplib2 is not positional. This would cause - spurious warnings in the logging. - - Making OAuth2Decorator more extensible. Fixes Issue 256. - - Update AdExchange Buyer API examples to version v1.2. - - -v1.1 - Version 1.1 - - Add PEM support to SignedJWTAssertionCredentials (used to only support - PKCS12 formatted keys). Note that if you use PEM formatted keys you can use - PyCrypto 2.6 or later instead of OpenSSL. - - Allow deserialized discovery docs to be passed to build_from_document(). - - - Make ResumableUploadError derive from HttpError. - - Many changes to move all the closures in apiclient.discovery into real - - classes and objects. - - Make from_json behavior inheritable. - - Expose the full token response in OAuth2Client and OAuth2Decorator. - - Handle reasons that are None. - - Added support for NDB based storing of oauth2client objects. - - Update grant_type for AssertionCredentials. - - Adding a .revoke() to Credentials. Closes issue 98. - - Modify oauth2client.multistore_file to store and retrieve credentials - using an arbitrary key. - - Don't accept 403 challenges by default for auth challenges. - - Set httplib2.RETRIES to 1. - - Consolidate handling of scopes. - - Upgrade to httplib2 version 0.8. - - Allow setting the response_type in OAuth2WebServerFlow. - - Ensure that dataWrapper feature is checked before using the 'data' value. - - HMAC verification does not use a constant time algorithm. - -v1.0 - Version 1.0 - - - Changes to the code for running tests and building releases. - -v1.0c3 - Version 1.0 Release Candidate 3 - - - In samples and oauth2 decorator, escape untrusted content before displaying it. - - Do not allow credentials files to be symlinks. - - Add XSRF protection to oauth2decorator callback 'state'. - - Handle uploading chunked media by stream. - - Handle passing streams directly to httplib2. - - Add support for Google Compute Engine service accounts. - - Flows no longer need to be saved between uses. - - Change GET to POST if URI is too long. Fixes issue #96. - - Add a keyring based Storage. - - More robust picking up JSON error responses. - - Make batch errors align with normal errors. - - Add a Google Compute sample. - - Token refresh to work with 'old' GData API - - Loading of client_secrets JSON file backed by a cache. - - Switch to new discovery path parameters. - - Add support for additionalProperties when printing schema'd objects. - - Fix media upload parameter names. Reviewed in http://codereview.appspot.com/6374062/ - - oauth2client support for URL-encoded format of exchange token response (e.g. Facebook) - - Build cleaner and easier to read docs for dynamic surfaces. - -v1.0c2 - Version 1.0 Release Candidate 2 - - - Parameter values of None should be treated as missing. Fixes issue #144. - - Distribute the samples separately from the library source. Fixes issue #155. - - Move all remaining samples over to client_secrets.json. Fixes issue #156. - - Make locked_file.py understand win32file primitives for better awesomeness. - -v1.0c1 - Version 1.0 Release Candidate 1 - - - Documentation for the library has switched to epydoc: - http://google-api-python-client.googlecode.com/hg/docs/epy/index.html - - Many improvements for media support: - * Added media download support, including resumable downloads. - * Better handling of streams that report their size as 0. - * Update Media Upload to include io.Base and also fix some bugs. - - OAuth bug fixes and improvements. - * Remove OAuth 1.0 support. - * Added credentials_from_code and credentials_from_clientsecrets_and_code. - * Make oauth2client support Windows-friendly locking. - * Fix bug in StorageByKeyName. - * Fix None handling in Django fields. Reviewed in http://codereview.appspot.com/6298084/. Fixes issue #128. - - Add epydoc generated docs. Reviewed in http://codereview.appspot.com/6305043/ - - Move to PEP386 compliant version numbers. - - New and updated samples - * Ad Exchange Buyer API v1 code samples. - * Automatically generate Samples wiki page from README files. - * Update Google Prediction samples. - * Add a Tasks sample that demonstrates Service accounts. - * new analytics api samples. Reviewed here: http://codereview.appspot.com/5494058/ - - Convert all inline samples to the Farm API for consistency. - -v1.0beta8 - - Updated meda upload support. - - Many fixes for batch requests. - - Better handling for requests that don't require a body. - - Fix issues with Google App Engine Python 2.7 runtime. - - Better support for proxies. - - All Storages now have a .delete() method. - - Important changes which might break your code: - * apiclient.anyjson has moved to oauth2client.anyjson. - * Some calls, for example, taskqueue().lease() used to require a parameter - named body. In this new release only methods that really need to send a body - require a body parameter, and so you may get errors about an unknown - 'body' parameter in your call. The solution is to remove the unneeded - body={} parameter. - -v1.0beta7 - - Support for batch requests. http://code.google.com/p/google-api-python-client/wiki/Batch - - Support for media upload. http://code.google.com/p/google-api-python-client/wiki/MediaUpload - - Better handling for APIs that return something other than JSON. - - Major cleanup and consolidation of the samples. - - Bug fixes and other enhancements: - 72 Defect Appengine OAuth2Decorator: Convert redirect address to string - 22 Defect Better error handling for unknown service name or version - 48 Defect StorageByKeyName().get() has side effects - 50 Defect Need sample client code for Admin Audit API - 28 Defect better comments for app engine sample Nov 9 - 63 Enhancement Let OAuth2Decorator take a list of scope diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..e90f668fbc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,216 @@ +# CHANGELOG + +## v1.4.5 + +* Set a shorter timeout for an Application Default Credentials issue on some + networks. (#93, #101) +* Test cleanup, switch from mox to mock. (#103) +* Switch docs to sphinx from epydoc. + +## v1.4.4 + +* Fix a bug in bytes/string encoding of headers. + +## v1.4.3 + +* Big thanks to @dhermes for spotting and fixing a mess in our test setup. + +* Fix a serious issue with tests not being run. (#86, #87, #89) +* Start credentials cleanup for single 2LO/3LO call. (#83, #84) +* Clean up stack traces when re-raising in some places. (#79) +* Clean up doc building. (#81, #82) +* Fixed minimum version for `six` dependency. (#75) + +## v1.4.2 + +* Several small bugfixes related to `six`/py3 support. + +## v1.4.1 + +* Fix a critical bug on import in `oauth2client.tools`. + +## v1.4 + +* Merge python3 branch! Massive thanks due to @pferate and @methane for doing + the heavy lifting. + +* Make `oauth2client.tools` import gracefully if `argparse` isn't present. + +* Change `flow.step2_exchange` to preserve the raw `id_token` in the + `token_response` field. + +## v1.3.2 + +* Quick bugfix for an issue with dict-like arguments to `flow.step2_exchange`, + which is common in some environments (such as GAE). + +## v1.3.1 + +* Quick bugfix for bad error handling in from_json. + +## v1.3 + +* Added support for the + [Google Application Default Credentials](https://developers.google.com/accounts/docs/application-default-credentials) + for more information (thanks @orestica). +* Added support for OAuth2 for devices (#3, thanks @sde-melo). +* The minimum required Python version is now 2.6. +* The `anyjson` submodule has been removed. + +* Better exception handling around missing crypto libraries (#56). +* Improve error messages in `AccessTokenRefreshError` (#53, thanks + @erickoledadevrel). +* Drop `uritemplate` as a dependency. +* Handle X509 certs with PyCrypto (#51, thanks @liujin-google). +* Handle additional failure types on OSX (#32, thanks @simoncadman). +* Better unicode handling with PKCS12 passwords (#31, thanks @jterrace). +* Better retry handling with bad server replies on refresh (#29, thanks + @kaste). +* Better logging for missing `refresh_token` in server replies (#21). +* Support `login_hint` (#18, thanks @jay0lee). +* Better overwrite options in `django_orm.Storage`. (#2, thanks @lraccomando). + + +## v1.2 + +* The use of the `gflags` library is now deprecated, and is no longer a + dependency. If you are still using the `oauth2client.tools.run()` function + then include `python-gflags` as a dependency of your application or switch to + `oauth2client.tools.run_flow`. +* Samples have been updated to use the new `apiclient.sample_tools`, and no + longer use `gflags`. +* Added support for the experimental Object Change Notification, as found in + the Cloud Storage API. +* The oauth2client App Engine decorators are now threadsafe. + +* Use the following redirects feature of httplib2 where it returns the + ultimate URL after a series of redirects to avoid multiple hops for every + resumable media upload request. +* Updated AdSense Management API samples to V1.3 +* Add option to automatically retry requests. +* Ability to list registered keys in `multistore_file`. +* User-agent must contain `(gzip)`. +* The `method` parameter for `httplib2` is not positional. This would cause + spurious warnings in the logging. +* Making OAuth2Decorator more extensible. Fixes Issue 256. +* Update AdExchange Buyer API examples to version v1.2. + + +## v1.1 + +* Add PEM support to `SignedJWTAssertionCredentials` (used to only support + PKCS12 formatted keys). Note that if you use PEM formatted keys you can use + PyCrypto 2.6 or later instead of OpenSSL. + +* Allow deserialized discovery docs to be passed to `build_from_document()`. + +* Make `ResumableUploadError` derive from `HttpError`. +* Many changes to move all the closures in `apiclient.discovery` into real + classes and objects. +* Make `from_json` behavior inheritable. +* Expose the full token response in `OAuth2Client` and `OAuth2Decorator`. +* Handle reasons that are None. +* Added support for NDB based storing of oauth2client objects. +* Update `grant_type` for `AssertionCredentials`. +* Adding a `.revoke()` to Credentials. Closes issue 98. +* Modify `oauth2client.multistore_file` to store and retrieve credentials + using an arbitrary key. +* Don't accept `403` challenges by default for auth challenges. +* Set `httplib2.RETRIES` to 1. +* Consolidate handling of scopes. +* Upgrade to httplib2 version 0.8. +* Allow setting the `response_type` in `OAuth2WebServerFlow`. +* Ensure that `dataWrapper` feature is checked before using the `data` value. +* HMAC verification does not use a constant time algorithm. + +## v1.0 + +* Changes to the code for running tests and building releases. + +## v1.0c3 + +* In samples and oauth2 decorator, escape untrusted content before displaying it. +* Do not allow credentials files to be symlinks. +* Add XSRF protection to oauth2decorator callback state. +* Handle uploading chunked media by stream. +* Handle passing streams directly to httplib2. +* Add support for Google Compute Engine service accounts. +* Flows no longer need to be saved between uses. +* Change GET to POST if URI is too long. Fixes issue 96. +* Add a `keyring`-based `Storage`. +* More robust picking up JSON error responses. +* Make batch errors align with normal errors. +* Add a Google Compute sample. +* Token refresh to work with old GData API. +* Loading of `client_secrets` JSON file backed by a cache. +* Switch to new discovery path parameters. +* Add support for `additionalProperties` when printing schema'd objects. +* [Fix media upload parameter names.](http://codereview.appspot.com/6374062/) +* oauth2client support for URL-encoded format of exchange token response (e.g. + Facebook) +* Build cleaner and easier to read docs for dynamic surfaces. + +## v1.0c2 + +* Parameter values of None should be treated as missing. Fixes issue 144. +* Distribute the samples separately from the library source. Fixes issue 155. +* Move all remaining samples over to `client_secrets.json`. Fixes issue 156. +* Make `locked_file.py` understand win32file primitives for better + awesomeness. + +## v1.0c1 + +* Documentation for the library has + [switched to epydoc](http://google-api-python-client.googlecode.com/hg/docs/epy/index.html) +* Many improvements for media support: + + Added media download support, including resumable downloads. + + Better handling of streams that report their size as 0. + + Update `MediaUpload` to include `io.Base` and also fix some bugs. +* OAuth bug fixes and improvements. + + Remove OAuth 1.0 support. + + Added `credentials_from_code` and `credentials_from_clientsecrets_and_code`. + + Make oauth2client support Windows-friendly locking. + + Fix bug in `StorageByKeyName`. + + Fix `None` handling in Django fields. + [Fixes issue 128](http://codereview.appspot.com/6298084/). +* [Add epydoc generated docs.](http://codereview.appspot.com/6305043/) +* Move to PEP386 compliant version numbers. +* New and updated samples + + Ad Exchange Buyer API v1 code samples. + + Automatically generate Samples wiki page from `README` files. + + Update Google Prediction samples. + + Add a Tasks sample that demonstrates Service accounts. + + [new analytics api samples.](http://codereview.appspot.com/5494058/) +* Convert all inline samples to the Farm API for consistency. + +## v1.0beta8 + +* Updated media upload support. +* Many fixes for batch requests. +* Better handling for requests that don't require a body. +* Fix issues with Google App Engine Python 2.7 runtime. +* Better support for proxies. +* All Storages now have a `.delete()` method. +* Important changes which might break your code: + + `apiclient.anyjson` has moved to `oauth2client.anyjson`. + + Some calls, for example, `taskqueue().lease()` used to require a parameter + named body. In this new release only methods that really need to send a + body require a body parameter, and so you may get errors about an unknown + `body` parameter in your call. The solution is to remove the unneeded + `body={}` parameter. + +## v1.0beta7 + +* Support for + [batch requests](http://code.google.com/p/google-api-python-client/wiki/Batch). +* Support for + [media upload](http://code.google.com/p/google-api-python-client/wiki/MediaUpload). +* Better handling for APIs that return something other than JSON. +* Major cleanup and consolidation of the samples. +* Bug fixes and other enhancements: + 72 Defect Appengine OAuth2Decorator: Convert redirect address to string + 22 Defect Better error handling for unknown service name or version + 48 Defect StorageByKeyName().get() has side effects + 50 Defect Need sample client code for Admin Audit API + 28 Defect better comments for app engine sample Nov 9 + 63 Enhancement Let OAuth2Decorator take a list of scope From f287b0561106867711e7e83b5154c3a28c41503c Mon Sep 17 00:00:00 2001 From: Nathaniel Manista Date: Fri, 16 Jan 2015 19:27:36 +0000 Subject: [PATCH 5/7] Release v1.4.6. --- CHANGELOG.md | 6 ++++++ oauth2client/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e90f668fbc..babc063b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## v1.4.6 + +* Add utility function to convert PKCS12 key to PEM. (#115) +* Change GCE detection logic. (#93) +* Add a tox env for doc generation. + ## v1.4.5 * Set a shorter timeout for an Application Default Credentials issue on some diff --git a/oauth2client/__init__.py b/oauth2client/__init__.py index 040f079378..b93b373227 100644 --- a/oauth2client/__init__.py +++ b/oauth2client/__init__.py @@ -1,6 +1,6 @@ """Client library for using OAuth2, especially with Google APIs.""" -__version__ = '1.4.5' +__version__ = '1.4.6' GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth' GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code' From 0a7cb24e68a4e5a7ed6754d42a14ab531dded7ae Mon Sep 17 00:00:00 2001 From: Nathan Naze Date: Sun, 18 Jan 2015 00:56:32 -0500 Subject: [PATCH 6/7] Fix comment on run_flow() -- flags is not an ArgumentParser, it is a Namespace, the result of calling parse_args() on an ArgumentParser object. This was a source of confusion when using the api. --- oauth2client/tools.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oauth2client/tools.py b/oauth2client/tools.py index 875064580a..23211c138e 100644 --- a/oauth2client/tools.py +++ b/oauth2client/tools.py @@ -147,7 +147,9 @@ def run_flow(flow, storage, flags, http=None): Args: flow: Flow, an OAuth 2.0 Flow to step through. storage: Storage, a Storage to store the credential in. - flags: argparse.ArgumentParser, the command-line flags. + flags: argparse.Namespace, The command-line flags. This is the object + returned from calling parse_args() on + argparse.ArgumentParser as described above. http: An instance of httplib2.Http.request or something that acts like it. From f438f5a4bd5784db2306475434e002a584c61ec1 Mon Sep 17 00:00:00 2001 From: Nathan Naze Date: Sun, 18 Jan 2015 00:07:01 -0500 Subject: [PATCH 7/7] Modify invalid exception error to be less cryptic and give more context to the developer. This is to fix my personal debugging experience when trying to debug the fact that I had downloaded the wrong client secret JSON type (not web/installed). --- oauth2client/clientsecrets.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py index b4c6f5217f..08a17020d1 100644 --- a/oauth2client/clientsecrets.py +++ b/oauth2client/clientsecrets.py @@ -69,8 +69,18 @@ class InvalidClientSecretsError(Error): def _validate_clientsecrets(obj): - if obj is None or len(obj) != 1: - raise InvalidClientSecretsError('Invalid file format.') + _INVALID_FILE_FORMAT_MSG = ( + 'Invalid file format. See ' + 'https://developers.google.com/api-client-library/' + 'python/guide/aaa_client_secrets') + + if obj is None: + raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG) + if len(obj) != 1: + raise InvalidClientSecretsError( + _INVALID_FILE_FORMAT_MSG + ' ' + 'Expected a JSON object with a single property for a "web" or ' + '"installed" application') client_type = tuple(obj)[0] if client_type not in VALID_CLIENT: raise InvalidClientSecretsError('Unknown client type: %s.' % (client_type,))