diff --git a/google/auth/_helpers.py b/google/auth/_helpers.py index 5a6c3a86b..de86beeb8 100644 --- a/google/auth/_helpers.py +++ b/google/auth/_helpers.py @@ -14,6 +14,7 @@ """Helper functions for commonly used utilities.""" +import base64 import calendar import datetime @@ -194,3 +195,19 @@ def string_to_scopes(scopes): return [] return scopes.split(' ') + + +def padded_urlsafe_b64decode(value): + """Decodes base64 strings lacking padding characters. + + Google infrastructure tends to omit the base64 padding characters. + + Args: + value (Union[str, bytes]): The encoded value. + + Returns: + bytes: The decoded value + """ + b64string = to_bytes(value) + padded = b64string + b'=' * (-len(b64string) % 4) + return base64.urlsafe_b64decode(padded) diff --git a/google/auth/jwt.py b/google/auth/jwt.py index 7abe4c2ee..0884b3ddb 100644 --- a/google/auth/jwt.py +++ b/google/auth/jwt.py @@ -96,7 +96,7 @@ def encode(signer, payload, header=None, key_id=None): def _decode_jwt_segment(encoded_section): """Decodes a single JWT segment.""" - section_bytes = base64.urlsafe_b64decode(encoded_section) + section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) try: return json.loads(section_bytes.decode('utf-8')) except ValueError: @@ -124,7 +124,7 @@ def _unverified_decode(token): encoded_header, encoded_payload, signature = token.split(b'.') signed_section = encoded_header + b'.' + encoded_payload - signature = base64.urlsafe_b64decode(signature) + signature = _helpers.padded_urlsafe_b64decode(signature) # Parse segments header = _decode_jwt_segment(encoded_header) diff --git a/google/oauth2/id_token.py b/google/oauth2/id_token.py index 968f27142..fa96fc032 100644 --- a/google/oauth2/id_token.py +++ b/google/oauth2/id_token.py @@ -47,7 +47,7 @@ def _fetch_certs(request, certs_url): Mapping[str, str]: A mapping of public key ID to x.509 certificate data. """ - response = request('GET', certs_url) + response = request(certs_url, method='GET') if response.status != http_client.OK: raise exceptions.TransportError( diff --git a/tests/oauth2/test_id_token.py b/tests/oauth2/test_id_token.py index f3da6632d..7f89547fb 100644 --- a/tests/oauth2/test_id_token.py +++ b/tests/oauth2/test_id_token.py @@ -18,6 +18,7 @@ import pytest from google.auth import exceptions +import google.auth.transport from google.oauth2 import id_token @@ -28,7 +29,7 @@ def make_request(status, data=None): if data is not None: response.data = json.dumps(data).encode('utf-8') - return mock.Mock(return_value=response) + return mock.Mock(return_value=response, spec=google.auth.transport.Request) def test__fetch_certs_success(): @@ -37,7 +38,7 @@ def test__fetch_certs_success(): returned_certs = id_token._fetch_certs(request, mock.sentinel.cert_url) - request.assert_called_once_with('GET', mock.sentinel.cert_url) + request.assert_called_once_with(mock.sentinel.cert_url, method='GET') assert returned_certs == certs @@ -47,7 +48,7 @@ def test__fetch_certs_failure(): with pytest.raises(exceptions.TransportError): id_token._fetch_certs(request, mock.sentinel.cert_url) - request.assert_called_once_with('GET', mock.sentinel.cert_url) + request.assert_called_once_with(mock.sentinel.cert_url, method='GET') @mock.patch('google.auth.jwt.decode', autospec=True) diff --git a/tests/test__helpers.py b/tests/test__helpers.py index d413adcab..b067faabf 100644 --- a/tests/test__helpers.py +++ b/tests/test__helpers.py @@ -151,3 +151,19 @@ def test_string_to_scopes(): for case, expected in cases: assert _helpers.string_to_scopes(case) == expected + + +def test_padded_urlsafe_b64decode(): + cases = [ + ('YQ==', b'a'), + ('YQ', b'a'), + ('YWE=', b'aa'), + ('YWE', b'aa'), + ('YWFhYQ==', b'aaaa'), + ('YWFhYQ', b'aaaa'), + ('YWFhYWE=', b'aaaaa'), + ('YWFhYWE', b'aaaaa'), + ] + + for case, expected in cases: + assert _helpers.padded_urlsafe_b64decode(case) == expected