From 8715c8417f89458553b622f9d6fed5f87f502185 Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 16:29:58 -0500 Subject: [PATCH 01/14] Update credentials.py --- google/oauth2/credentials.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py index 4cb909cb6..6461f8f9c 100644 --- a/google/oauth2/credentials.py +++ b/google/oauth2/credentials.py @@ -51,7 +51,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): def __init__(self, token, refresh_token=None, id_token=None, token_uri=None, client_id=None, client_secret=None, - scopes=None): + scopes=None, downscope=False): """ Args: token (Optional(str)): The OAuth 2.0 access token. Can be None @@ -71,6 +71,10 @@ def __init__(self, token, refresh_token=None, id_token=None, to obtain authorization. This is a purely informative parameter that can be used by :meth:`has_scopes`. OAuth 2.0 credentials can not request additional scopes after authorization. + downscope (bool): Whether to reduce the requested scopes from those + of the refresh token to those listed in scopes. Useful if + refresh token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). """ super(Credentials, self).__init__() self.token = token @@ -80,6 +84,7 @@ def __init__(self, token, refresh_token=None, id_token=None, self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret + self._downscope = downscope @property def refresh_token(self): @@ -130,10 +135,11 @@ def refresh(self, request): 'refresh the access token. You must specify refresh_token, ' 'token_uri, client_id, and client_secret.') + scopes = self._scopes if self._downscope else None access_token, refresh_token, expiry, grant_response = ( _client.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, - self._client_secret)) + self._client_secret, scopes)) self.token = access_token self.expiry = expiry @@ -141,7 +147,7 @@ def refresh(self, request): self._id_token = grant_response.get('id_token') @classmethod - def from_authorized_user_info(cls, info, scopes=None): + def from_authorized_user_info(cls, info, scopes=None, downscope=False): """Creates a Credentials instance from parsed authorized user info. Args: @@ -149,6 +155,10 @@ def from_authorized_user_info(cls, info, scopes=None): format. scopes (Sequence[str]): Optional list of scopes to include in the credentials. + downscope (bool): Whether to reduce the requested scopes from those + of the refresh token to those listed in scopes. Useful if + refresh token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). Returns: google.oauth2.credentials.Credentials: The constructed @@ -171,7 +181,8 @@ def from_authorized_user_info(cls, info, scopes=None): token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, scopes=scopes, client_id=info['client_id'], - client_secret=info['client_secret']) + client_secret=info['client_secret'], + downscope=downscope) @classmethod def from_authorized_user_file(cls, filename, scopes=None): @@ -181,6 +192,10 @@ def from_authorized_user_file(cls, filename, scopes=None): filename (str): The path to the authorized user json file. scopes (Sequence[str]): Optional list of scopes to include in the credentials. + downscope (bool): Whether to reduce the requested scopes from those + of the refresh token to those listed in scopes. Useful if + refresh token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). Returns: google.oauth2.credentials.Credentials: The constructed @@ -191,4 +206,4 @@ def from_authorized_user_file(cls, filename, scopes=None): """ with io.open(filename, 'r', encoding='utf-8') as json_file: data = json.load(json_file) - return cls.from_authorized_user_info(data, scopes) + return cls.from_authorized_user_info(data, scopes, downscope) From f01d9ae5ecc3202b35028804caedf0eaae6a497c Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 16:35:14 -0500 Subject: [PATCH 02/14] Update _client.py --- google/oauth2/_client.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/google/oauth2/_client.py b/google/oauth2/_client.py index dc35be271..c1c19ce30 100644 --- a/google/oauth2/_client.py +++ b/google/oauth2/_client.py @@ -201,7 +201,8 @@ def id_token_jwt_grant(request, token_uri, assertion): return id_token, expiry, response_data -def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): +def refresh_grant(request, token_uri, refresh_token, client_id, client_secret, + scopes=None): """Implements the OAuth 2.0 refresh token grant. For more details, see `rfc678 section 6`_. @@ -215,6 +216,10 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): token. client_id (str): The OAuth 2.0 application's client ID. client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). Returns: Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The @@ -233,6 +238,8 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret): 'client_secret': client_secret, 'refresh_token': refresh_token, } + if scopes: + body['scope'] = ' '.join(scopes) response_data = _token_endpoint_request(request, token_uri, body) From 076613007856e4995d01c0acbddcf088bcfdbc32 Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 16:39:41 -0500 Subject: [PATCH 03/14] Update test_credentials.py --- tests/oauth2/test_credentials.py | 47 +++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/oauth2/test_credentials.py b/tests/oauth2/test_credentials.py index 922c3bbf7..ec046badb 100644 --- a/tests/oauth2/test_credentials.py +++ b/tests/oauth2/test_credentials.py @@ -86,7 +86,7 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # Check jwt grant call. refresh_grant.assert_called_with( request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, - self.CLIENT_SECRET) + self.CLIENT_SECRET, None) # Check that the credentials have the token and expiry assert credentials.token == token @@ -97,6 +97,51 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # expired) assert credentials.valid + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + def test_refresh_success_with_downscoping(self, unused_utcnow, + refresh_grant): + scopes = ['email', 'profile'] + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, scopes=scopes, + downscope=True) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET, scopes) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + def test_refresh_no_refresh_token(self): request = mock.create_autospec(transport.Request) credentials_ = credentials.Credentials( From 1e04ce39a49a78936b2511cbb7f32845efa8c3a9 Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 16:43:27 -0500 Subject: [PATCH 04/14] Update test__client.py --- tests/oauth2/test__client.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/oauth2/test__client.py b/tests/oauth2/test__client.py index 3ec7fc62a..5a4a56745 100644 --- a/tests/oauth2/test__client.py +++ b/tests/oauth2/test__client.py @@ -37,6 +37,11 @@ SIGNER = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1') +SCOPES_AS_LIST = ['https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/logging.write'] +SCOPES_AS_STRING = ('https://www.googleapis.com/auth/pubsub' + ' https://www.googleapis.com/auth/logging.write') + def test__handle_error_response(): response_data = json.dumps({ @@ -204,6 +209,35 @@ def test_refresh_grant(unused_utcnow): assert extra_data['extra'] == 'data' +@mock.patch('google.auth._helpers.utcnow', return_value=datetime.datetime.min) +def test_refresh_grant_with_scopes(unused_utcnow): + request = make_request({ + 'access_token': 'token', + 'refresh_token': 'new_refresh_token', + 'expires_in': 500, + 'extra': 'data', + 'scope': SCOPES_AS_STRING}) + + token, refresh_token, expiry, extra_data = _client.refresh_grant( + request, 'http://example.com', 'refresh_token', 'client_id', + 'client_secret', SCOPES_AS_LIST) + + # Check request call. + verify_request_params(request, { + 'grant_type': _client._REFRESH_GRANT_TYPE, + 'refresh_token': 'refresh_token', + 'client_id': 'client_id', + 'client_secret': 'client_secret', + 'scope': SCOPES_AS_STRING + }) + + # Check result. + assert token == 'token' + assert refresh_token == 'new_refresh_token' + assert expiry == datetime.datetime.min + datetime.timedelta(seconds=500) + assert extra_data['extra'] == 'data' + + def test_refresh_grant_no_access_token(): request = make_request({ # No access token. From 9205cc8b49da0586c392002e08577540e17abc41 Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 19:23:03 -0500 Subject: [PATCH 05/14] Update credentials.py --- google/oauth2/credentials.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py index 6461f8f9c..7c0c7f8e1 100644 --- a/google/oauth2/credentials.py +++ b/google/oauth2/credentials.py @@ -85,7 +85,7 @@ def __init__(self, token, refresh_token=None, id_token=None, self._client_id = client_id self._client_secret = client_secret self._downscope = downscope - +how to install python 3 into "tox_testing" bin @property def refresh_token(self): """Optional[str]: The OAuth 2.0 refresh token.""" @@ -185,7 +185,7 @@ def from_authorized_user_info(cls, info, scopes=None, downscope=False): downscope=downscope) @classmethod - def from_authorized_user_file(cls, filename, scopes=None): + def from_authorized_user_file(cls, filename, scopes=None, downscope=False): """Creates a Credentials instance from an authorized user json file. Args: From 296e61597c17edb349ae799cd2026e41ff1016dd Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 19:27:27 -0500 Subject: [PATCH 06/14] Update credentials.py --- google/oauth2/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py index 7c0c7f8e1..641d82ca4 100644 --- a/google/oauth2/credentials.py +++ b/google/oauth2/credentials.py @@ -85,7 +85,7 @@ def __init__(self, token, refresh_token=None, id_token=None, self._client_id = client_id self._client_secret = client_secret self._downscope = downscope -how to install python 3 into "tox_testing" bin + @property def refresh_token(self): """Optional[str]: The OAuth 2.0 refresh token.""" From dbaa8cb7fc15c6b2c20147da275a061a084ea118 Mon Sep 17 00:00:00 2001 From: "Eugene W. Foley" Date: Mon, 26 Nov 2018 19:55:36 -0500 Subject: [PATCH 07/14] Update _client.py --- google/oauth2/_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/oauth2/_client.py b/google/oauth2/_client.py index c1c19ce30..5121a3274 100644 --- a/google/oauth2/_client.py +++ b/google/oauth2/_client.py @@ -239,7 +239,7 @@ def refresh_grant(request, token_uri, refresh_token, client_id, client_secret, 'refresh_token': refresh_token, } if scopes: - body['scope'] = ' '.join(scopes) + body['scope'] = ' '.join(scopes) response_data = _token_endpoint_request(request, token_uri, body) From dad9326edd79fc54d207738c0d10fbe7522f1e7d Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Mon, 26 Nov 2018 20:02:40 -0500 Subject: [PATCH 08/14] doc update --- docs/reference/google.auth.app_engine.rst | 4 ++-- .../google.auth.compute_engine.credentials.rst | 4 ++-- docs/reference/google.auth.compute_engine.rst | 4 ++-- docs/reference/google.auth.crypt.rst | 13 +++++++++++-- docs/reference/google.auth.environment_vars.rst | 4 ++-- docs/reference/google.auth.rst | 2 +- docs/reference/google.oauth2.id_token.rst | 4 ++-- docs/reference/google.oauth2.service_account.rst | 4 ++-- 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/docs/reference/google.auth.app_engine.rst b/docs/reference/google.auth.app_engine.rst index 4525c089e..16594b47e 100644 --- a/docs/reference/google.auth.app_engine.rst +++ b/docs/reference/google.auth.app_engine.rst @@ -1,5 +1,5 @@ -google.auth.app_engine module -============================= +google.auth.app\_engine module +============================== .. automodule:: google.auth.app_engine :members: diff --git a/docs/reference/google.auth.compute_engine.credentials.rst b/docs/reference/google.auth.compute_engine.credentials.rst index ebe62c07c..358779b83 100644 --- a/docs/reference/google.auth.compute_engine.credentials.rst +++ b/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,5 +1,5 @@ -google.auth.compute_engine.credentials module -============================================= +google.auth.compute\_engine.credentials module +============================================== .. automodule:: google.auth.compute_engine.credentials :members: diff --git a/docs/reference/google.auth.compute_engine.rst b/docs/reference/google.auth.compute_engine.rst index 2e7b830db..64d13dbb5 100644 --- a/docs/reference/google.auth.compute_engine.rst +++ b/docs/reference/google.auth.compute_engine.rst @@ -1,5 +1,5 @@ -google.auth.compute_engine package -================================== +google.auth.compute\_engine package +=================================== .. automodule:: google.auth.compute_engine :members: diff --git a/docs/reference/google.auth.crypt.rst b/docs/reference/google.auth.crypt.rst index 26b8b4ac4..a3e2b1206 100644 --- a/docs/reference/google.auth.crypt.rst +++ b/docs/reference/google.auth.crypt.rst @@ -1,7 +1,16 @@ -google.auth.crypt module -======================== +google.auth.crypt package +========================= .. automodule:: google.auth.crypt :members: :inherited-members: :show-inheritance: + +Submodules +---------- + +.. toctree:: + + google.auth.crypt.base + google.auth.crypt.rsa + diff --git a/docs/reference/google.auth.environment_vars.rst b/docs/reference/google.auth.environment_vars.rst index fe34849dd..8e639c492 100644 --- a/docs/reference/google.auth.environment_vars.rst +++ b/docs/reference/google.auth.environment_vars.rst @@ -1,5 +1,5 @@ -google.auth.environment_vars module -=================================== +google.auth.environment\_vars module +==================================== .. automodule:: google.auth.environment_vars :members: diff --git a/docs/reference/google.auth.rst b/docs/reference/google.auth.rst index bc6740b09..53ab699c6 100644 --- a/docs/reference/google.auth.rst +++ b/docs/reference/google.auth.rst @@ -12,6 +12,7 @@ Subpackages .. toctree:: google.auth.compute_engine + google.auth.crypt google.auth.transport Submodules @@ -21,7 +22,6 @@ Submodules google.auth.app_engine google.auth.credentials - google.auth.crypt google.auth.environment_vars google.auth.exceptions google.auth.iam diff --git a/docs/reference/google.oauth2.id_token.rst b/docs/reference/google.oauth2.id_token.rst index db38b6085..98f2d36dd 100644 --- a/docs/reference/google.oauth2.id_token.rst +++ b/docs/reference/google.oauth2.id_token.rst @@ -1,5 +1,5 @@ -google.oauth2.id_token module -============================= +google.oauth2.id\_token module +============================== .. automodule:: google.oauth2.id_token :members: diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst index cc4e43899..a805ac9ee 100644 --- a/docs/reference/google.oauth2.service_account.rst +++ b/docs/reference/google.oauth2.service_account.rst @@ -1,5 +1,5 @@ -google.oauth2.service_account module -==================================== +google.oauth2.service\_account module +===================================== .. automodule:: google.oauth2.service_account :members: From c89bbe4ebcf9421ca8505d3149f272c4edf14279 Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Tue, 27 Nov 2018 13:12:18 -0500 Subject: [PATCH 09/14] Remove backslashes added by tox doc tools. --- docs/reference/google.auth.app_engine.rst | 2 +- docs/reference/google.auth.compute_engine.credentials.rst | 2 +- docs/reference/google.auth.compute_engine.rst | 2 +- docs/reference/google.auth.environment_vars.rst | 2 +- docs/reference/google.oauth2.id_token.rst | 2 +- docs/reference/google.oauth2.service_account.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/google.auth.app_engine.rst b/docs/reference/google.auth.app_engine.rst index 16594b47e..fe5296fef 100644 --- a/docs/reference/google.auth.app_engine.rst +++ b/docs/reference/google.auth.app_engine.rst @@ -1,4 +1,4 @@ -google.auth.app\_engine module +google.auth.app_engine module ============================== .. automodule:: google.auth.app_engine diff --git a/docs/reference/google.auth.compute_engine.credentials.rst b/docs/reference/google.auth.compute_engine.credentials.rst index 358779b83..76d27d99b 100644 --- a/docs/reference/google.auth.compute_engine.credentials.rst +++ b/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,4 +1,4 @@ -google.auth.compute\_engine.credentials module +google.auth.compute_engine.credentials module ============================================== .. automodule:: google.auth.compute_engine.credentials diff --git a/docs/reference/google.auth.compute_engine.rst b/docs/reference/google.auth.compute_engine.rst index 64d13dbb5..a3c7f17bf 100644 --- a/docs/reference/google.auth.compute_engine.rst +++ b/docs/reference/google.auth.compute_engine.rst @@ -1,4 +1,4 @@ -google.auth.compute\_engine package +google.auth.compute_engine package =================================== .. automodule:: google.auth.compute_engine diff --git a/docs/reference/google.auth.environment_vars.rst b/docs/reference/google.auth.environment_vars.rst index 8e639c492..175e52b77 100644 --- a/docs/reference/google.auth.environment_vars.rst +++ b/docs/reference/google.auth.environment_vars.rst @@ -1,4 +1,4 @@ -google.auth.environment\_vars module +google.auth.environment_vars module ==================================== .. automodule:: google.auth.environment_vars diff --git a/docs/reference/google.oauth2.id_token.rst b/docs/reference/google.oauth2.id_token.rst index 98f2d36dd..1f1b8fa8b 100644 --- a/docs/reference/google.oauth2.id_token.rst +++ b/docs/reference/google.oauth2.id_token.rst @@ -1,4 +1,4 @@ -google.oauth2.id\_token module +google.oauth2.id_token module ============================== .. automodule:: google.oauth2.id_token diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst index a805ac9ee..7d095ff63 100644 --- a/docs/reference/google.oauth2.service_account.rst +++ b/docs/reference/google.oauth2.service_account.rst @@ -1,4 +1,4 @@ -google.oauth2.service\_account module +google.oauth2.service_account module ===================================== .. automodule:: google.oauth2.service_account From f13636b371f2389fe3be7c1206adad81d30e9db3 Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Tue, 27 Nov 2018 13:17:34 -0500 Subject: [PATCH 10/14] Remove = added by tox doc tools. --- docs/reference/google.auth.app_engine.rst | 2 +- docs/reference/google.auth.compute_engine.credentials.rst | 2 +- docs/reference/google.auth.compute_engine.rst | 2 +- docs/reference/google.auth.environment_vars.rst | 2 +- docs/reference/google.oauth2.id_token.rst | 2 +- docs/reference/google.oauth2.service_account.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/google.auth.app_engine.rst b/docs/reference/google.auth.app_engine.rst index fe5296fef..4525c089e 100644 --- a/docs/reference/google.auth.app_engine.rst +++ b/docs/reference/google.auth.app_engine.rst @@ -1,5 +1,5 @@ google.auth.app_engine module -============================== +============================= .. automodule:: google.auth.app_engine :members: diff --git a/docs/reference/google.auth.compute_engine.credentials.rst b/docs/reference/google.auth.compute_engine.credentials.rst index 76d27d99b..ebe62c07c 100644 --- a/docs/reference/google.auth.compute_engine.credentials.rst +++ b/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,5 +1,5 @@ google.auth.compute_engine.credentials module -============================================== +============================================= .. automodule:: google.auth.compute_engine.credentials :members: diff --git a/docs/reference/google.auth.compute_engine.rst b/docs/reference/google.auth.compute_engine.rst index a3c7f17bf..2e7b830db 100644 --- a/docs/reference/google.auth.compute_engine.rst +++ b/docs/reference/google.auth.compute_engine.rst @@ -1,5 +1,5 @@ google.auth.compute_engine package -=================================== +================================== .. automodule:: google.auth.compute_engine :members: diff --git a/docs/reference/google.auth.environment_vars.rst b/docs/reference/google.auth.environment_vars.rst index 175e52b77..fe34849dd 100644 --- a/docs/reference/google.auth.environment_vars.rst +++ b/docs/reference/google.auth.environment_vars.rst @@ -1,5 +1,5 @@ google.auth.environment_vars module -==================================== +=================================== .. automodule:: google.auth.environment_vars :members: diff --git a/docs/reference/google.oauth2.id_token.rst b/docs/reference/google.oauth2.id_token.rst index 1f1b8fa8b..db38b6085 100644 --- a/docs/reference/google.oauth2.id_token.rst +++ b/docs/reference/google.oauth2.id_token.rst @@ -1,5 +1,5 @@ google.oauth2.id_token module -============================== +============================= .. automodule:: google.oauth2.id_token :members: diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst index 7d095ff63..cc4e43899 100644 --- a/docs/reference/google.oauth2.service_account.rst +++ b/docs/reference/google.oauth2.service_account.rst @@ -1,5 +1,5 @@ google.oauth2.service_account module -===================================== +==================================== .. automodule:: google.oauth2.service_account :members: From d74398638c69b714bec5167b51e99c3616104e3f Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Fri, 1 Feb 2019 18:34:55 -0500 Subject: [PATCH 11/14] Create downscoped credentials via bound method downscope() rather than via classmethods from_authorized_user_info() and from_authorized_user_file() --- docs/reference/google.auth.app_engine.rst | 4 +- ...google.auth.compute_engine.credentials.rst | 4 +- docs/reference/google.auth.compute_engine.rst | 4 +- .../google.auth.environment_vars.rst | 4 +- docs/reference/google.oauth2.id_token.rst | 4 +- .../google.oauth2.service_account.rst | 4 +- google/oauth2/credentials.py | 45 +++++++++++++------ tests/oauth2/test_credentials.py | 40 ++++++++--------- 8 files changed, 64 insertions(+), 45 deletions(-) diff --git a/docs/reference/google.auth.app_engine.rst b/docs/reference/google.auth.app_engine.rst index 4525c089e..16594b47e 100644 --- a/docs/reference/google.auth.app_engine.rst +++ b/docs/reference/google.auth.app_engine.rst @@ -1,5 +1,5 @@ -google.auth.app_engine module -============================= +google.auth.app\_engine module +============================== .. automodule:: google.auth.app_engine :members: diff --git a/docs/reference/google.auth.compute_engine.credentials.rst b/docs/reference/google.auth.compute_engine.credentials.rst index ebe62c07c..358779b83 100644 --- a/docs/reference/google.auth.compute_engine.credentials.rst +++ b/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,5 +1,5 @@ -google.auth.compute_engine.credentials module -============================================= +google.auth.compute\_engine.credentials module +============================================== .. automodule:: google.auth.compute_engine.credentials :members: diff --git a/docs/reference/google.auth.compute_engine.rst b/docs/reference/google.auth.compute_engine.rst index 2e7b830db..64d13dbb5 100644 --- a/docs/reference/google.auth.compute_engine.rst +++ b/docs/reference/google.auth.compute_engine.rst @@ -1,5 +1,5 @@ -google.auth.compute_engine package -================================== +google.auth.compute\_engine package +=================================== .. automodule:: google.auth.compute_engine :members: diff --git a/docs/reference/google.auth.environment_vars.rst b/docs/reference/google.auth.environment_vars.rst index fe34849dd..8e639c492 100644 --- a/docs/reference/google.auth.environment_vars.rst +++ b/docs/reference/google.auth.environment_vars.rst @@ -1,5 +1,5 @@ -google.auth.environment_vars module -=================================== +google.auth.environment\_vars module +==================================== .. automodule:: google.auth.environment_vars :members: diff --git a/docs/reference/google.oauth2.id_token.rst b/docs/reference/google.oauth2.id_token.rst index db38b6085..98f2d36dd 100644 --- a/docs/reference/google.oauth2.id_token.rst +++ b/docs/reference/google.oauth2.id_token.rst @@ -1,5 +1,5 @@ -google.oauth2.id_token module -============================= +google.oauth2.id\_token module +============================== .. automodule:: google.oauth2.id_token :members: diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst index cc4e43899..a805ac9ee 100644 --- a/docs/reference/google.oauth2.service_account.rst +++ b/docs/reference/google.oauth2.service_account.rst @@ -1,5 +1,5 @@ -google.oauth2.service_account module -==================================== +google.oauth2.service\_account module +===================================== .. automodule:: google.oauth2.service_account :members: diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py index 641d82ca4..0844636c1 100644 --- a/google/oauth2/credentials.py +++ b/google/oauth2/credentials.py @@ -146,8 +146,36 @@ def refresh(self, request): self._refresh_token = refresh_token self._id_token = grant_response.get('id_token') + def downscope(self, scopes): + """Creates a Credentials instance with reduced access token scope. + + The requested scopes must be derivable from the current refresh + token. For example, a refresh token with a wild card scope like + 'https://www.googleapis.com/auth/any-api' could be used to request + access tokens with 'https://www.googleapis.com/auth/pubsub'. + + Args: + scopes (Sequence[str]): The scopes to request for the new + credential's access tokens. Access token scope will be limited + to those in the new credentials scopes property, even if the + associated refresh token's scope is broader. + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + """ + scoped_credentials = Credentials( + None, + refresh_token=self._refresh_token, + token_uri=self._token_uri, + client_id=self._client_id, + client_secret=self._client_secret, + scopes=scopes, + downscope=True) + + return scoped_credentials + @classmethod - def from_authorized_user_info(cls, info, scopes=None, downscope=False): + def from_authorized_user_info(cls, info, scopes=None): """Creates a Credentials instance from parsed authorized user info. Args: @@ -155,10 +183,6 @@ def from_authorized_user_info(cls, info, scopes=None, downscope=False): format. scopes (Sequence[str]): Optional list of scopes to include in the credentials. - downscope (bool): Whether to reduce the requested scopes from those - of the refresh token to those listed in scopes. Useful if - refresh token has a wild card scope (e.g. - 'https://www.googleapis.com/auth/any-api'). Returns: google.oauth2.credentials.Credentials: The constructed @@ -181,21 +205,16 @@ def from_authorized_user_info(cls, info, scopes=None, downscope=False): token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, scopes=scopes, client_id=info['client_id'], - client_secret=info['client_secret'], - downscope=downscope) + client_secret=info['client_secret']) @classmethod - def from_authorized_user_file(cls, filename, scopes=None, downscope=False): + def from_authorized_user_file(cls, filename, scopes=None): """Creates a Credentials instance from an authorized user json file. Args: filename (str): The path to the authorized user json file. scopes (Sequence[str]): Optional list of scopes to include in the credentials. - downscope (bool): Whether to reduce the requested scopes from those - of the refresh token to those listed in scopes. Useful if - refresh token has a wild card scope (e.g. - 'https://www.googleapis.com/auth/any-api'). Returns: google.oauth2.credentials.Credentials: The constructed @@ -206,4 +225,4 @@ def from_authorized_user_file(cls, filename, scopes=None, downscope=False): """ with io.open(filename, 'r', encoding='utf-8') as json_file: data = json.load(json_file) - return cls.from_authorized_user_info(data, scopes, downscope) + return cls.from_authorized_user_info(data, scopes) diff --git a/tests/oauth2/test_credentials.py b/tests/oauth2/test_credentials.py index ec046badb..2d755451f 100644 --- a/tests/oauth2/test_credentials.py +++ b/tests/oauth2/test_credentials.py @@ -97,12 +97,22 @@ def test_refresh_success(self, unused_utcnow, refresh_grant): # expired) assert credentials.valid + def test_refresh_no_refresh_token(self): + request = mock.create_autospec(transport.Request) + credentials_ = credentials.Credentials( + token=None, refresh_token=None) + + with pytest.raises(exceptions.RefreshError, match='necessary fields'): + credentials_.refresh(request) + + request.assert_not_called() + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) - def test_refresh_success_with_downscoping(self, unused_utcnow, - refresh_grant): + def test_downsoped_credentials_refresh_success(self, unused_utcnow, + refresh_grant): scopes = ['email', 'profile'] token = 'token' expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) @@ -121,11 +131,11 @@ def test_refresh_success_with_downscoping(self, unused_utcnow, creds = credentials.Credentials( token=None, refresh_token=self.REFRESH_TOKEN, token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET, scopes=scopes, - downscope=True) + client_secret=self.CLIENT_SECRET) + downscoped_creds = creds.downscope(scopes=scopes) # Refresh credentials - creds.refresh(request) + downscoped_creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( @@ -133,24 +143,14 @@ def test_refresh_success_with_downscoping(self, unused_utcnow, self.CLIENT_SECRET, scopes) # Check that the credentials have the token and expiry - assert creds.token == token - assert creds.expiry == expiry - assert creds.id_token == mock.sentinel.id_token - assert creds.has_scopes(scopes) + assert downscoped_creds.token == token + assert downscoped_creds.expiry == expiry + assert downscoped_creds.id_token == mock.sentinel.id_token + assert downscoped_creds.has_scopes(scopes) # Check that the credentials are valid (have a token and are not # expired.) - assert creds.valid - - def test_refresh_no_refresh_token(self): - request = mock.create_autospec(transport.Request) - credentials_ = credentials.Credentials( - token=None, refresh_token=None) - - with pytest.raises(exceptions.RefreshError, match='necessary fields'): - credentials_.refresh(request) - - request.assert_not_called() + assert downscoped_creds.valid def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy() From e561c2870f5da16f58c30698691f17bad886f7eb Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Fri, 1 Feb 2019 18:48:57 -0500 Subject: [PATCH 12/14] remove \ added by tox -e docgen --- docs/reference/google.auth.app_engine.rst | 2 +- docs/reference/google.auth.compute_engine.credentials.rst | 2 +- docs/reference/google.auth.compute_engine.rst | 2 +- docs/reference/google.auth.environment_vars.rst | 2 +- docs/reference/google.oauth2.id_token.rst | 2 +- docs/reference/google.oauth2.service_account.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/google.auth.app_engine.rst b/docs/reference/google.auth.app_engine.rst index 16594b47e..fe5296fef 100644 --- a/docs/reference/google.auth.app_engine.rst +++ b/docs/reference/google.auth.app_engine.rst @@ -1,4 +1,4 @@ -google.auth.app\_engine module +google.auth.app_engine module ============================== .. automodule:: google.auth.app_engine diff --git a/docs/reference/google.auth.compute_engine.credentials.rst b/docs/reference/google.auth.compute_engine.credentials.rst index 358779b83..76d27d99b 100644 --- a/docs/reference/google.auth.compute_engine.credentials.rst +++ b/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,4 +1,4 @@ -google.auth.compute\_engine.credentials module +google.auth.compute_engine.credentials module ============================================== .. automodule:: google.auth.compute_engine.credentials diff --git a/docs/reference/google.auth.compute_engine.rst b/docs/reference/google.auth.compute_engine.rst index 64d13dbb5..a3c7f17bf 100644 --- a/docs/reference/google.auth.compute_engine.rst +++ b/docs/reference/google.auth.compute_engine.rst @@ -1,4 +1,4 @@ -google.auth.compute\_engine package +google.auth.compute_engine package =================================== .. automodule:: google.auth.compute_engine diff --git a/docs/reference/google.auth.environment_vars.rst b/docs/reference/google.auth.environment_vars.rst index 8e639c492..175e52b77 100644 --- a/docs/reference/google.auth.environment_vars.rst +++ b/docs/reference/google.auth.environment_vars.rst @@ -1,4 +1,4 @@ -google.auth.environment\_vars module +google.auth.environment_vars module ==================================== .. automodule:: google.auth.environment_vars diff --git a/docs/reference/google.oauth2.id_token.rst b/docs/reference/google.oauth2.id_token.rst index 98f2d36dd..1f1b8fa8b 100644 --- a/docs/reference/google.oauth2.id_token.rst +++ b/docs/reference/google.oauth2.id_token.rst @@ -1,4 +1,4 @@ -google.oauth2.id\_token module +google.oauth2.id_token module ============================== .. automodule:: google.oauth2.id_token diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst index a805ac9ee..7d095ff63 100644 --- a/docs/reference/google.oauth2.service_account.rst +++ b/docs/reference/google.oauth2.service_account.rst @@ -1,4 +1,4 @@ -google.oauth2.service\_account module +google.oauth2.service_account module ===================================== .. automodule:: google.oauth2.service_account From d27df98de3bbd9ad8095733fb4def494fce5dee3 Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Fri, 1 Feb 2019 18:52:09 -0500 Subject: [PATCH 13/14] remove = added by tox -e docgen --- docs/reference/google.auth.app_engine.rst | 2 +- docs/reference/google.auth.compute_engine.credentials.rst | 2 +- docs/reference/google.auth.compute_engine.rst | 2 +- docs/reference/google.auth.environment_vars.rst | 2 +- docs/reference/google.oauth2.id_token.rst | 2 +- docs/reference/google.oauth2.service_account.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/google.auth.app_engine.rst b/docs/reference/google.auth.app_engine.rst index fe5296fef..4525c089e 100644 --- a/docs/reference/google.auth.app_engine.rst +++ b/docs/reference/google.auth.app_engine.rst @@ -1,5 +1,5 @@ google.auth.app_engine module -============================== +============================= .. automodule:: google.auth.app_engine :members: diff --git a/docs/reference/google.auth.compute_engine.credentials.rst b/docs/reference/google.auth.compute_engine.credentials.rst index 76d27d99b..ebe62c07c 100644 --- a/docs/reference/google.auth.compute_engine.credentials.rst +++ b/docs/reference/google.auth.compute_engine.credentials.rst @@ -1,5 +1,5 @@ google.auth.compute_engine.credentials module -============================================== +============================================= .. automodule:: google.auth.compute_engine.credentials :members: diff --git a/docs/reference/google.auth.compute_engine.rst b/docs/reference/google.auth.compute_engine.rst index a3c7f17bf..2e7b830db 100644 --- a/docs/reference/google.auth.compute_engine.rst +++ b/docs/reference/google.auth.compute_engine.rst @@ -1,5 +1,5 @@ google.auth.compute_engine package -=================================== +================================== .. automodule:: google.auth.compute_engine :members: diff --git a/docs/reference/google.auth.environment_vars.rst b/docs/reference/google.auth.environment_vars.rst index 175e52b77..fe34849dd 100644 --- a/docs/reference/google.auth.environment_vars.rst +++ b/docs/reference/google.auth.environment_vars.rst @@ -1,5 +1,5 @@ google.auth.environment_vars module -==================================== +=================================== .. automodule:: google.auth.environment_vars :members: diff --git a/docs/reference/google.oauth2.id_token.rst b/docs/reference/google.oauth2.id_token.rst index 1f1b8fa8b..db38b6085 100644 --- a/docs/reference/google.oauth2.id_token.rst +++ b/docs/reference/google.oauth2.id_token.rst @@ -1,5 +1,5 @@ google.oauth2.id_token module -============================== +============================= .. automodule:: google.oauth2.id_token :members: diff --git a/docs/reference/google.oauth2.service_account.rst b/docs/reference/google.oauth2.service_account.rst index 7d095ff63..cc4e43899 100644 --- a/docs/reference/google.oauth2.service_account.rst +++ b/docs/reference/google.oauth2.service_account.rst @@ -1,5 +1,5 @@ google.oauth2.service_account module -===================================== +==================================== .. automodule:: google.oauth2.service_account :members: From 2845de8d90de8d61565d1bdf5f27fca6e20de364 Mon Sep 17 00:00:00 2001 From: Eugene Foley Date: Thu, 9 May 2019 15:36:38 -0400 Subject: [PATCH 14/14] Credentials always request explicit scopes, if provided. --- google/oauth2/credentials.py | 58 ++++++---------- tests/oauth2/test_credentials.py | 112 ++++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 49 deletions(-) diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py index 0844636c1..b56e31426 100644 --- a/google/oauth2/credentials.py +++ b/google/oauth2/credentials.py @@ -51,7 +51,7 @@ class Credentials(credentials.ReadOnlyScoped, credentials.Credentials): def __init__(self, token, refresh_token=None, id_token=None, token_uri=None, client_id=None, client_secret=None, - scopes=None, downscope=False): + scopes=None): """ Args: token (Optional(str)): The OAuth 2.0 access token. Can be None @@ -67,14 +67,13 @@ def __init__(self, token, refresh_token=None, id_token=None, client_secret(str): The OAuth 2.0 client secret. Must be specified for refresh, can be left as None if the token can not be refreshed. - scopes (Sequence[str]): The scopes that were originally used - to obtain authorization. This is a purely informative parameter - that can be used by :meth:`has_scopes`. OAuth 2.0 credentials - can not request additional scopes after authorization. - downscope (bool): Whether to reduce the requested scopes from those - of the refresh token to those listed in scopes. Useful if - refresh token has a wild card scope (e.g. - 'https://www.googleapis.com/auth/any-api'). + scopes (Sequence[str]): The scopes used to obtain authorization. + This parameter is used by :meth:`has_scopes`. OAuth 2.0 + credentials can not request additional scopes after + authorization. The scopes must be derivable from the refresh + token if refresh information is provided (e.g. The refresh + token scopes are a superset of this or contain a wild card + scope like 'https://www.googleapis.com/auth/any-api'). """ super(Credentials, self).__init__() self.token = token @@ -84,7 +83,6 @@ def __init__(self, token, refresh_token=None, id_token=None, self._token_uri = token_uri self._client_id = client_id self._client_secret = client_secret - self._downscope = downscope @property def refresh_token(self): @@ -135,44 +133,26 @@ def refresh(self, request): 'refresh the access token. You must specify refresh_token, ' 'token_uri, client_id, and client_secret.') - scopes = self._scopes if self._downscope else None access_token, refresh_token, expiry, grant_response = ( _client.refresh_grant( request, self._token_uri, self._refresh_token, self._client_id, - self._client_secret, scopes)) + self._client_secret, self._scopes)) self.token = access_token self.expiry = expiry self._refresh_token = refresh_token self._id_token = grant_response.get('id_token') - def downscope(self, scopes): - """Creates a Credentials instance with reduced access token scope. - - The requested scopes must be derivable from the current refresh - token. For example, a refresh token with a wild card scope like - 'https://www.googleapis.com/auth/any-api' could be used to request - access tokens with 'https://www.googleapis.com/auth/pubsub'. - - Args: - scopes (Sequence[str]): The scopes to request for the new - credential's access tokens. Access token scope will be limited - to those in the new credentials scopes property, even if the - associated refresh token's scope is broader. - Returns: - google.oauth2.credentials.Credentials: The constructed - credentials. - """ - scoped_credentials = Credentials( - None, - refresh_token=self._refresh_token, - token_uri=self._token_uri, - client_id=self._client_id, - client_secret=self._client_secret, - scopes=scopes, - downscope=True) - - return scoped_credentials + if self._scopes and 'scopes' in grant_response: + requested_scopes = frozenset(self._scopes) + granted_scopes = frozenset(grant_response['scopes'].split()) + scopes_requested_but_not_granted = ( + requested_scopes - granted_scopes) + if scopes_requested_but_not_granted: + raise exceptions.RefreshError( + 'Not all requested scopes were granted by the ' + 'authorization server, missing scopes {}.'.format( + ', '.join(scopes_requested_but_not_granted))) @classmethod def from_authorized_user_info(cls, info, scopes=None): diff --git a/tests/oauth2/test_credentials.py b/tests/oauth2/test_credentials.py index 2d755451f..32315096a 100644 --- a/tests/oauth2/test_credentials.py +++ b/tests/oauth2/test_credentials.py @@ -111,8 +111,8 @@ def test_refresh_no_refresh_token(self): @mock.patch( 'google.auth._helpers.utcnow', return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) - def test_downsoped_credentials_refresh_success(self, unused_utcnow, - refresh_grant): + def test_credentials_with_scopes_requested_refresh_success( + self, unused_utcnow, refresh_grant): scopes = ['email', 'profile'] token = 'token' expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) @@ -131,11 +131,10 @@ def test_downsoped_credentials_refresh_success(self, unused_utcnow, creds = credentials.Credentials( token=None, refresh_token=self.REFRESH_TOKEN, token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, - client_secret=self.CLIENT_SECRET) - downscoped_creds = creds.downscope(scopes=scopes) + client_secret=self.CLIENT_SECRET, scopes=scopes) # Refresh credentials - downscoped_creds.refresh(request) + creds.refresh(request) # Check jwt grant call. refresh_grant.assert_called_with( @@ -143,14 +142,107 @@ def test_downsoped_credentials_refresh_success(self, unused_utcnow, self.CLIENT_SECRET, scopes) # Check that the credentials have the token and expiry - assert downscoped_creds.token == token - assert downscoped_creds.expiry == expiry - assert downscoped_creds.id_token == mock.sentinel.id_token - assert downscoped_creds.has_scopes(scopes) + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) # Check that the credentials are valid (have a token and are not # expired.) - assert downscoped_creds.valid + assert creds.valid + + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + def test_credentials_with_scopes_returned_refresh_success( + self, unused_utcnow, refresh_grant): + scopes = ['email', 'profile'] + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token, + 'scopes': ' '.join(scopes)} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, scopes=scopes) + + # Refresh credentials + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET, scopes) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid + + @mock.patch('google.oauth2._client.refresh_grant', autospec=True) + @mock.patch( + 'google.auth._helpers.utcnow', + return_value=datetime.datetime.min + _helpers.CLOCK_SKEW) + def test_credentials_with_scopes_refresh_failure_raises_refresh_error( + self, unused_utcnow, refresh_grant): + scopes = ['email', 'profile'] + scopes_returned = ['email'] + token = 'token' + expiry = _helpers.utcnow() + datetime.timedelta(seconds=500) + grant_response = {'id_token': mock.sentinel.id_token, + 'scopes': ' '.join(scopes_returned)} + refresh_grant.return_value = ( + # Access token + token, + # New refresh token + None, + # Expiry, + expiry, + # Extra data + grant_response) + + request = mock.create_autospec(transport.Request) + creds = credentials.Credentials( + token=None, refresh_token=self.REFRESH_TOKEN, + token_uri=self.TOKEN_URI, client_id=self.CLIENT_ID, + client_secret=self.CLIENT_SECRET, scopes=scopes) + + # Refresh credentials + with pytest.raises(exceptions.RefreshError, + match='Not all requested scopes were granted'): + creds.refresh(request) + + # Check jwt grant call. + refresh_grant.assert_called_with( + request, self.TOKEN_URI, self.REFRESH_TOKEN, self.CLIENT_ID, + self.CLIENT_SECRET, scopes) + + # Check that the credentials have the token and expiry + assert creds.token == token + assert creds.expiry == expiry + assert creds.id_token == mock.sentinel.id_token + assert creds.has_scopes(scopes) + + # Check that the credentials are valid (have a token and are not + # expired.) + assert creds.valid def test_from_authorized_user_info(self): info = AUTH_USER_INFO.copy()