Skip to content

Commit

Permalink
fix: add clock_skew_in_seconds to verify_token functions (#894)
Browse files Browse the repository at this point in the history
  • Loading branch information
arithmetic1728 committed Oct 25, 2021
1 parent 2fa8cc5 commit 8e95c1e
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 12 deletions.
29 changes: 25 additions & 4 deletions google/oauth2/_id_token_async.py
Expand Up @@ -99,7 +99,11 @@ async def _fetch_certs(request, certs_url):


async def verify_token(
id_token, request, audience=None, certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL
id_token,
request,
audience=None,
certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=0,
):
"""Verifies an ID token and returns the decoded token.
Expand All @@ -112,16 +116,25 @@ async def verify_token(
certs_url (str): The URL that specifies the certificates to use to
verify the token. This URL should return JSON in the format of
``{'key id': 'x509 certificate'}``.
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.
Returns:
Mapping[str, Any]: The decoded token.
"""
certs = await _fetch_certs(request, certs_url)

return jwt.decode(id_token, certs=certs, audience=audience)
return jwt.decode(
id_token,
certs=certs,
audience=audience,
clock_skew_in_seconds=clock_skew_in_seconds,
)


async def verify_oauth2_token(id_token, request, audience=None):
async def verify_oauth2_token(
id_token, request, audience=None, clock_skew_in_seconds=0
):
"""Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
Args:
Expand All @@ -131,6 +144,8 @@ async def verify_oauth2_token(id_token, request, audience=None):
audience (str): The audience that this token is intended for. This is
typically your application's OAuth 2.0 client ID. If None then the
audience is not verified.
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.
Returns:
Mapping[str, Any]: The decoded token.
Expand All @@ -143,6 +158,7 @@ async def verify_oauth2_token(id_token, request, audience=None):
request,
audience=audience,
certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=clock_skew_in_seconds,
)

if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS:
Expand All @@ -155,7 +171,9 @@ async def verify_oauth2_token(id_token, request, audience=None):
return idinfo


async def verify_firebase_token(id_token, request, audience=None):
async def verify_firebase_token(
id_token, request, audience=None, clock_skew_in_seconds=0
):
"""Verifies an ID Token issued by Firebase Authentication.
Args:
Expand All @@ -165,6 +183,8 @@ async def verify_firebase_token(id_token, request, audience=None):
audience (str): The audience that this token is intended for. This is
typically your Firebase application ID. If None then the audience
is not verified.
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.
Returns:
Mapping[str, Any]: The decoded token.
Expand All @@ -174,6 +194,7 @@ async def verify_firebase_token(id_token, request, audience=None):
request,
audience=audience,
certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL,
clock_skew_in_seconds=clock_skew_in_seconds,
)


Expand Down
37 changes: 31 additions & 6 deletions google/oauth2/id_token.py
Expand Up @@ -105,7 +105,13 @@ def _fetch_certs(request, certs_url):
return json.loads(response.data.decode("utf-8"))


def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL):
def verify_token(
id_token,
request,
audience=None,
certs_url=_GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=0,
):
"""Verifies an ID token and returns the decoded token.
Args:
Expand All @@ -117,16 +123,23 @@ def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERT
certs_url (str): The URL that specifies the certificates to use to
verify the token. This URL should return JSON in the format of
``{'key id': 'x509 certificate'}``.
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.
Returns:
Mapping[str, Any]: The decoded token.
"""
certs = _fetch_certs(request, certs_url)

return jwt.decode(id_token, certs=certs, audience=audience)
return jwt.decode(
id_token,
certs=certs,
audience=audience,
clock_skew_in_seconds=clock_skew_in_seconds,
)


def verify_oauth2_token(id_token, request, audience=None):
def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0):
"""Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
Args:
Expand All @@ -136,6 +149,8 @@ def verify_oauth2_token(id_token, request, audience=None):
audience (str): The audience that this token is intended for. This is
typically your application's OAuth 2.0 client ID. If None then the
audience is not verified.
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.
Returns:
Mapping[str, Any]: The decoded token.
Expand All @@ -144,7 +159,11 @@ def verify_oauth2_token(id_token, request, audience=None):
exceptions.GoogleAuthError: If the issuer is invalid.
"""
idinfo = verify_token(
id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL
id_token,
request,
audience=audience,
certs_url=_GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=clock_skew_in_seconds,
)

if idinfo["iss"] not in _GOOGLE_ISSUERS:
Expand All @@ -157,7 +176,7 @@ def verify_oauth2_token(id_token, request, audience=None):
return idinfo


def verify_firebase_token(id_token, request, audience=None):
def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0):
"""Verifies an ID Token issued by Firebase Authentication.
Args:
Expand All @@ -167,12 +186,18 @@ def verify_firebase_token(id_token, request, audience=None):
audience (str): The audience that this token is intended for. This is
typically your Firebase application ID. If None then the audience
is not verified.
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
validation.
Returns:
Mapping[str, Any]: The decoded token.
"""
return verify_token(
id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL
id_token,
request,
audience=audience,
certs_url=_GOOGLE_APIS_CERTS_URL,
clock_skew_in_seconds=clock_skew_in_seconds,
)


Expand Down
68 changes: 67 additions & 1 deletion tests/oauth2/test_id_token.py
Expand Up @@ -71,7 +71,10 @@ def test_verify_token(_fetch_certs, decode):
mock.sentinel.request, id_token._GOOGLE_OAUTH2_CERTS_URL
)
decode.assert_called_once_with(
mock.sentinel.token, certs=_fetch_certs.return_value, audience=None
mock.sentinel.token,
certs=_fetch_certs.return_value,
audience=None,
clock_skew_in_seconds=0,
)


Expand All @@ -91,6 +94,28 @@ def test_verify_token_args(_fetch_certs, decode):
mock.sentinel.token,
certs=_fetch_certs.return_value,
audience=mock.sentinel.audience,
clock_skew_in_seconds=0,
)


@mock.patch("google.auth.jwt.decode", autospec=True)
@mock.patch("google.oauth2.id_token._fetch_certs", autospec=True)
def test_verify_token_clock_skew(_fetch_certs, decode):
result = id_token.verify_token(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=mock.sentinel.certs_url,
clock_skew_in_seconds=10,
)

assert result == decode.return_value
_fetch_certs.assert_called_once_with(mock.sentinel.request, mock.sentinel.certs_url)
decode.assert_called_once_with(
mock.sentinel.token,
certs=_fetch_certs.return_value,
audience=mock.sentinel.audience,
clock_skew_in_seconds=10,
)


Expand All @@ -107,6 +132,27 @@ def test_verify_oauth2_token(verify_token):
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=0,
)


@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
def test_verify_oauth2_token_clock_skew(verify_token):
verify_token.return_value = {"iss": "accounts.google.com"}
result = id_token.verify_oauth2_token(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
clock_skew_in_seconds=10,
)

assert result == verify_token.return_value
verify_token.assert_called_once_with(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=id_token._GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=10,
)


Expand All @@ -132,6 +178,26 @@ def test_verify_firebase_token(verify_token):
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=id_token._GOOGLE_APIS_CERTS_URL,
clock_skew_in_seconds=0,
)


@mock.patch("google.oauth2.id_token.verify_token", autospec=True)
def test_verify_firebase_token_clock_skew(verify_token):
result = id_token.verify_firebase_token(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
clock_skew_in_seconds=10,
)

assert result == verify_token.return_value
verify_token.assert_called_once_with(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=id_token._GOOGLE_APIS_CERTS_URL,
clock_skew_in_seconds=10,
)


Expand Down
69 changes: 68 additions & 1 deletion tests_async/oauth2/test_id_token.py
Expand Up @@ -71,7 +71,30 @@ async def test_verify_token(_fetch_certs, decode):
mock.sentinel.request, sync_id_token._GOOGLE_OAUTH2_CERTS_URL
)
decode.assert_called_once_with(
mock.sentinel.token, certs=_fetch_certs.return_value, audience=None
mock.sentinel.token,
certs=_fetch_certs.return_value,
audience=None,
clock_skew_in_seconds=0,
)


@mock.patch("google.auth.jwt.decode", autospec=True)
@mock.patch("google.oauth2._id_token_async._fetch_certs", autospec=True)
@pytest.mark.asyncio
async def test_verify_token_clock_skew(_fetch_certs, decode):
result = await id_token.verify_token(
mock.sentinel.token, mock.sentinel.request, clock_skew_in_seconds=10
)

assert result == decode.return_value
_fetch_certs.assert_called_once_with(
mock.sentinel.request, sync_id_token._GOOGLE_OAUTH2_CERTS_URL
)
decode.assert_called_once_with(
mock.sentinel.token,
certs=_fetch_certs.return_value,
audience=None,
clock_skew_in_seconds=10,
)


Expand All @@ -92,6 +115,7 @@ async def test_verify_token_args(_fetch_certs, decode):
mock.sentinel.token,
certs=_fetch_certs.return_value,
audience=mock.sentinel.audience,
clock_skew_in_seconds=0,
)


Expand All @@ -109,6 +133,28 @@ async def test_verify_oauth2_token(verify_token):
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=0,
)


@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True)
@pytest.mark.asyncio
async def test_verify_oauth2_token_clock_skew(verify_token):
verify_token.return_value = {"iss": "accounts.google.com"}
result = await id_token.verify_oauth2_token(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
clock_skew_in_seconds=10,
)

assert result == verify_token.return_value
verify_token.assert_called_once_with(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL,
clock_skew_in_seconds=10,
)


Expand Down Expand Up @@ -136,6 +182,27 @@ async def test_verify_firebase_token(verify_token):
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL,
clock_skew_in_seconds=0,
)


@mock.patch("google.oauth2._id_token_async.verify_token", autospec=True)
@pytest.mark.asyncio
async def test_verify_firebase_token_clock_skew(verify_token):
result = await id_token.verify_firebase_token(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
clock_skew_in_seconds=10,
)

assert result == verify_token.return_value
verify_token.assert_called_once_with(
mock.sentinel.token,
mock.sentinel.request,
audience=mock.sentinel.audience,
certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL,
clock_skew_in_seconds=10,
)


Expand Down

0 comments on commit 8e95c1e

Please sign in to comment.