Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit e60c124

Browse files
author
Jon Wayne Parrott
authored
Add a consistent 5 minute clock skew accomodation (#145)
1 parent f0bc072 commit e60c124

7 files changed

Lines changed: 42 additions & 15 deletions

File tree

google/auth/_helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
from six.moves import urllib
2323

2424

25+
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
26+
CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
27+
28+
2529
def copy_docstring(source_class):
2630
"""Decorator that copies a method's docstring from another class.
2731

google/auth/credentials.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ def expired(self):
5656
Note that credentials can be invalid but not expired becaue Credentials
5757
with :attr:`expiry` set to None is considered to never expire.
5858
"""
59-
now = _helpers.utcnow()
60-
return self.expiry is not None and self.expiry <= now
59+
# Err on the side of reporting expiration early so that we avoid
60+
# the 403-refresh-retry loop.
61+
adjusted_now = _helpers.utcnow() - _helpers.CLOCK_SKEW
62+
return self.expiry is not None and self.expiry <= adjusted_now
6163

6264
@property
6365
def valid(self):

google/auth/jwt.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@
5252
import google.auth.credentials
5353

5454

55-
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
56-
_CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
55+
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
5756

5857

5958
def encode(signer, payload, header=None, key_id=None):
@@ -161,21 +160,25 @@ def _verify_iat_and_exp(payload):
161160
"""
162161
now = _helpers.datetime_to_secs(_helpers.utcnow())
163162

164-
# Make sure the iat and exp claims are present
163+
# Make sure the iat and exp claims are present.
165164
for key in ('iat', 'exp'):
166165
if key not in payload:
167166
raise ValueError(
168167
'Token does not contain required claim {}'.format(key))
169168

170-
# Make sure the token wasn't issued in the future
169+
# Make sure the token wasn't issued in the future.
171170
iat = payload['iat']
172-
earliest = iat - _CLOCK_SKEW_SECS
171+
# Err on the side of accepting a token that is slightly early to account
172+
# for clock skew.
173+
earliest = iat - _helpers.CLOCK_SKEW_SECS
173174
if now < earliest:
174175
raise ValueError('Token used too early, {} < {}'.format(now, iat))
175176

176-
# Make sure the token wasn't issue in the past
177+
# Make sure the token wasn't issued in the past.
177178
exp = payload['exp']
178-
latest = exp + _CLOCK_SKEW_SECS
179+
# Err on the side of accepting a token that is slightly out of date
180+
# to account for clow skew.
181+
latest = exp + _helpers.CLOCK_SKEW_SECS
179182
if latest < now:
180183
raise ValueError('Token expired, {} < {}'.format(latest, now))
181184

tests/compute_engine/test_credentials.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import mock
1818
import pytest
1919

20+
from google.auth import _helpers
2021
from google.auth import exceptions
2122
from google.auth.compute_engine import credentials
2223

@@ -38,7 +39,8 @@ def test_default_state(self):
3839
assert self.credentials.service_account_email == 'default'
3940

4041
@mock.patch(
41-
'google.auth._helpers.utcnow', return_value=datetime.datetime.min)
42+
'google.auth._helpers.utcnow',
43+
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW)
4244
@mock.patch('google.auth.compute_engine._metadata.get')
4345
def test_refresh_success(self, get_mock, now_mock):
4446
get_mock.side_effect = [{
@@ -57,7 +59,7 @@ def test_refresh_success(self, get_mock, now_mock):
5759
# Check that the credentials have the token and proper expiration
5860
assert self.credentials.token == 'token'
5961
assert self.credentials.expiry == (
60-
datetime.datetime.min + datetime.timedelta(seconds=500))
62+
now_mock() + datetime.timedelta(seconds=500))
6163

6264
# Check the credential info
6365
assert (self.credentials.service_account_email ==

tests/oauth2/test_credentials.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ def test_create_scoped(self):
5353

5454
@mock.patch('google.oauth2._client.refresh_grant', autospec=True)
5555
@mock.patch(
56-
'google.auth._helpers.utcnow', return_value=datetime.datetime.min)
56+
'google.auth._helpers.utcnow',
57+
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW)
5758
def test_refresh_success(self, now_mock, refresh_grant_mock):
5859
token = 'token'
5960
expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)

tests/test_app_engine.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import mock
1818
import pytest
1919

20+
from google.auth import _helpers
2021
from google.auth import app_engine
2122

2223

@@ -111,7 +112,7 @@ def test_service_account_email_explicit(self, app_identity_mock):
111112

112113
@mock.patch(
113114
'google.auth._helpers.utcnow',
114-
return_value=datetime.datetime.min)
115+
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW)
115116
def test_refresh(self, now_mock, app_identity_mock):
116117
token = 'token'
117118
ttl = 100
@@ -124,7 +125,7 @@ def test_refresh(self, now_mock, app_identity_mock):
124125
credentials.scopes, credentials._service_account_id)
125126
assert credentials.token == token
126127
assert credentials.expiry == (
127-
datetime.datetime.min + datetime.timedelta(seconds=ttl))
128+
now_mock() + datetime.timedelta(seconds=ttl))
128129
assert credentials.valid
129130
assert not credentials.expired
130131

tests/test_credentials.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import datetime
1616

17+
from google.auth import _helpers
1718
from google.auth import credentials
1819

1920

@@ -37,8 +38,21 @@ def test_expired_and_valid():
3738
assert credentials.valid
3839
assert not credentials.expired
3940

41+
# Set the expiration in the past, but because of clock skew accomodation
42+
# the credentials should still be valid.
4043
credentials.expiry = (
41-
datetime.datetime.utcnow() - datetime.timedelta(seconds=60))
44+
datetime.datetime.utcnow() -
45+
datetime.timedelta(seconds=1))
46+
47+
assert credentials.valid
48+
assert not credentials.expired
49+
50+
# Set the credentials far enough in the past to exceed the clock skew
51+
# accomodation. They should now be expired.
52+
credentials.expiry = (
53+
datetime.datetime.utcnow() -
54+
_helpers.CLOCK_SKEW -
55+
datetime.timedelta(seconds=1))
4256

4357
assert not credentials.valid
4458
assert credentials.expired

0 commit comments

Comments
 (0)