Fix ID token verification (#87)
diff --git a/google/auth/_helpers.py b/google/auth/_helpers.py
index 5a6c3a8..de86bee 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 @@
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 7abe4c2..0884b3d 100644
--- a/google/auth/jwt.py
+++ b/google/auth/jwt.py
@@ -96,7 +96,7 @@
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 @@
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 968f271..fa96fc0 100644
--- a/google/oauth2/id_token.py
+++ b/google/oauth2/id_token.py
@@ -47,7 +47,7 @@
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 f3da663..7f89547 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 @@
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 @@
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 @@
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 d413adc..b067faa 100644
--- a/tests/test__helpers.py
+++ b/tests/test__helpers.py
@@ -151,3 +151,19 @@
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