Skip to content

Commit 738611b

Browse files
fix: rename CLOCK_SKEW and separate client/server user case (#863)
* fix: rename CLOCK_SKEW and separate client/server user case * update clock skew to 20s
1 parent 13aed5f commit 738611b

15 files changed

+82
-35
lines changed

google/auth/_helpers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import urllib
2121

2222

23-
CLOCK_SKEW_SECS = 60 # 60 seconds
24-
CLOCK_SKEW = datetime.timedelta(seconds=CLOCK_SKEW_SECS)
23+
# Token server doesn't provide a new a token when doing refresh unless the
24+
# token is expiring within 30 seconds, so refresh threshold should not be
25+
# more than 30 seconds. Otherwise auth lib will send tons of refresh requests
26+
# until 30 seconds before the expiration, and cause a spike of CPU usage.
27+
REFRESH_THRESHOLD = datetime.timedelta(seconds=20)
2528

2629

2730
def copy_docstring(source_class):

google/auth/credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def expired(self):
6262

6363
# Remove 10 seconds from expiry to err on the side of reporting
6464
# expiration early so that we avoid the 401-refresh-retry loop.
65-
skewed_expiry = self.expiry - _helpers.CLOCK_SKEW
65+
skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD
6666
return _helpers.utcnow() >= skewed_expiry
6767

6868
@property

google/auth/jwt.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ def decode_header(token):
167167
return header
168168

169169

170-
def _verify_iat_and_exp(payload):
170+
def _verify_iat_and_exp(payload, clock_skew_in_seconds=0):
171171
"""Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
172172
payload.
173173
174174
Args:
175175
payload (Mapping[str, str]): The JWT payload.
176+
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
177+
validation.
176178
177179
Raises:
178180
ValueError: if any checks failed.
@@ -188,7 +190,7 @@ def _verify_iat_and_exp(payload):
188190
iat = payload["iat"]
189191
# Err on the side of accepting a token that is slightly early to account
190192
# for clock skew.
191-
earliest = iat - _helpers.CLOCK_SKEW_SECS
193+
earliest = iat - clock_skew_in_seconds
192194
if now < earliest:
193195
raise ValueError(
194196
"Token used too early, {} < {}. Check that your computer's clock is set correctly.".format(
@@ -200,12 +202,12 @@ def _verify_iat_and_exp(payload):
200202
exp = payload["exp"]
201203
# Err on the side of accepting a token that is slightly out of date
202204
# to account for clow skew.
203-
latest = exp + _helpers.CLOCK_SKEW_SECS
205+
latest = exp + clock_skew_in_seconds
204206
if latest < now:
205207
raise ValueError("Token expired, {} < {}".format(latest, now))
206208

207209

208-
def decode(token, certs=None, verify=True, audience=None):
210+
def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0):
209211
"""Decode and verify a JWT.
210212
211213
Args:
@@ -221,6 +223,8 @@ def decode(token, certs=None, verify=True, audience=None):
221223
audience (str or list): The audience claim, 'aud', that this JWT should
222224
contain. Or a list of audience claims. If None then the JWT's 'aud'
223225
parameter is not verified.
226+
clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
227+
validation.
224228
225229
Returns:
226230
Mapping[str, str]: The deserialized JSON payload in the JWT.
@@ -271,7 +275,7 @@ def decode(token, certs=None, verify=True, audience=None):
271275
raise ValueError("Could not verify token signature.")
272276

273277
# Verify the issued at and created times in the payload.
274-
_verify_iat_and_exp(payload)
278+
_verify_iat_and_exp(payload, clock_skew_in_seconds)
275279

276280
# Check audience.
277281
if audience is not None:

google/oauth2/credentials.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def refresh(self, request):
270270
raise exceptions.RefreshError(
271271
"The refresh_handler returned expiry is not a datetime object."
272272
)
273-
if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW:
273+
if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD:
274274
raise exceptions.RefreshError(
275275
"The credentials returned by the refresh_handler are "
276276
"already expired."
@@ -359,7 +359,7 @@ def from_authorized_user_info(cls, info, scopes=None):
359359
expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
360360
)
361361
else:
362-
expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW
362+
expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD
363363

364364
# process scopes, which needs to be a seq
365365
if scopes is None and "scopes" in info:

tests/compute_engine/test_credentials.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def test_default_state(self):
6464

6565
@mock.patch(
6666
"google.auth._helpers.utcnow",
67-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
67+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
6868
)
6969
@mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
7070
def test_refresh_success(self, get, utcnow):
@@ -98,7 +98,7 @@ def test_refresh_success(self, get, utcnow):
9898

9999
@mock.patch(
100100
"google.auth._helpers.utcnow",
101-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
101+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
102102
)
103103
@mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
104104
def test_refresh_success_with_scopes(self, get, utcnow):

tests/oauth2/test_credentials.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def test_invalid_refresh_handler(self):
115115
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
116116
@mock.patch(
117117
"google.auth._helpers.utcnow",
118-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
118+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
119119
)
120120
def test_refresh_success(self, unused_utcnow, refresh_grant):
121121
token = "token"
@@ -175,7 +175,7 @@ def test_refresh_no_refresh_token(self):
175175
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
176176
@mock.patch(
177177
"google.auth._helpers.utcnow",
178-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
178+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
179179
)
180180
def test_refresh_with_refresh_token_and_refresh_handler(
181181
self, unused_utcnow, refresh_grant
@@ -361,7 +361,7 @@ def test_refresh_with_refresh_handler_invalid_expiry(self):
361361

362362
@mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
363363
def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow):
364-
expected_expiry = datetime.datetime.min + _helpers.CLOCK_SKEW
364+
expected_expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
365365
# Simulate refresh handler returns an expired token.
366366
refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry))
367367
scopes = ["email", "profile"]
@@ -391,7 +391,7 @@ def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow):
391391
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
392392
@mock.patch(
393393
"google.auth._helpers.utcnow",
394-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
394+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
395395
)
396396
def test_credentials_with_scopes_requested_refresh_success(
397397
self, unused_utcnow, refresh_grant
@@ -457,7 +457,7 @@ def test_credentials_with_scopes_requested_refresh_success(
457457
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
458458
@mock.patch(
459459
"google.auth._helpers.utcnow",
460-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
460+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
461461
)
462462
def test_credentials_with_only_default_scopes_requested(
463463
self, unused_utcnow, refresh_grant
@@ -521,7 +521,7 @@ def test_credentials_with_only_default_scopes_requested(
521521
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
522522
@mock.patch(
523523
"google.auth._helpers.utcnow",
524-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
524+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
525525
)
526526
def test_credentials_with_scopes_returned_refresh_success(
527527
self, unused_utcnow, refresh_grant
@@ -588,7 +588,7 @@ def test_credentials_with_scopes_returned_refresh_success(
588588
@mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
589589
@mock.patch(
590590
"google.auth._helpers.utcnow",
591-
return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
591+
return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
592592
)
593593
def test_credentials_with_scopes_refresh_failure_raises_refresh_error(
594594
self, unused_utcnow, refresh_grant

tests/test_credentials.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ def test_expired_and_valid():
4646
# Set the expiration to one second more than now plus the clock skew
4747
# accomodation. These credentials should be valid.
4848
credentials.expiry = (
49-
datetime.datetime.utcnow() + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
49+
datetime.datetime.utcnow()
50+
+ _helpers.REFRESH_THRESHOLD
51+
+ datetime.timedelta(seconds=1)
5052
)
5153

5254
assert credentials.valid

tests/test_downscoped.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,9 @@ def test_before_request_expired(self, utcnow):
669669
# Set the expiration to one second more than now plus the clock skew
670670
# accommodation. These credentials should be valid.
671671
credentials.expiry = (
672-
datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
672+
datetime.datetime.min
673+
+ _helpers.REFRESH_THRESHOLD
674+
+ datetime.timedelta(seconds=1)
673675
)
674676

675677
assert credentials.valid

tests/test_external_account.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,9 @@ def test_before_request_expired(self, utcnow):
976976
# Set the expiration to one second more than now plus the clock skew
977977
# accomodation. These credentials should be valid.
978978
credentials.expiry = (
979-
datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
979+
datetime.datetime.min
980+
+ _helpers.REFRESH_THRESHOLD
981+
+ datetime.timedelta(seconds=1)
980982
)
981983

982984
assert credentials.valid
@@ -1027,7 +1029,9 @@ def test_before_request_impersonation_expired(self, utcnow):
10271029
# Set the expiration to one second more than now plus the clock skew
10281030
# accomodation. These credentials should be valid.
10291031
credentials.expiry = (
1030-
datetime.datetime.min + _helpers.CLOCK_SKEW + datetime.timedelta(seconds=1)
1032+
datetime.datetime.min
1033+
+ _helpers.REFRESH_THRESHOLD
1034+
+ datetime.timedelta(seconds=1)
10311035
)
10321036

10331037
assert credentials.valid

tests/test_iam.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def __init__(self):
4545
super(CredentialsImpl, self).__init__()
4646
self.token = "token"
4747
# Force refresh
48-
self.expiry = datetime.datetime.min + _helpers.CLOCK_SKEW
48+
self.expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
4949

5050
def refresh(self, request):
5151
pass

0 commit comments

Comments
 (0)