Skip to content
This repository was archived by the owner on Jan 18, 2025. It is now read-only.

Commit d93ed1e

Browse files
committed
Merge pull request #173 from thobrla/master
Avoid using expired tokens from credential storage.
2 parents 3a742ee + 21c9be1 commit d93ed1e

2 files changed

Lines changed: 68 additions & 15 deletions

File tree

oauth2client/client.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -557,14 +557,20 @@ def new_request(uri, method='GET', body=None, headers=None,
557557
resp, content = request_orig(uri, method, body, clean_headers(headers),
558558
redirections, connection_type)
559559

560-
if resp.status in REFRESH_STATUS_CODES:
561-
logger.info('Refreshing due to a %s', resp.status)
560+
# A stored token may expire between the time it is retrieved and the time
561+
# the request is made, so we may need to try twice.
562+
max_refresh_attempts = 2
563+
for refresh_attempt in range(max_refresh_attempts):
564+
if resp.status not in REFRESH_STATUS_CODES:
565+
break
566+
logger.info('Refreshing due to a %s (attempt %s/%s)', resp.status,
567+
refresh_attempt + 1, max_refresh_attempts)
562568
self._refresh(request_orig)
563569
self.apply(headers)
564-
return request_orig(uri, method, body, clean_headers(headers),
565-
redirections, connection_type)
566-
else:
567-
return (resp, content)
570+
resp, content = request_orig(uri, method, body, clean_headers(headers),
571+
redirections, connection_type)
572+
573+
return (resp, content)
568574

569575
# Replace the request method with our own closure.
570576
http.request = new_request
@@ -757,8 +763,10 @@ def _refresh(self, http_request):
757763
self.store.acquire_lock()
758764
try:
759765
new_cred = self.store.locked_get()
766+
760767
if (new_cred and not new_cred.invalid and
761-
new_cred.access_token != self.access_token):
768+
new_cred.access_token != self.access_token and
769+
not new_cred.access_token_expired):
762770
logger.info('Updated access_token read from Storage')
763771
self._updateFromCredential(new_cred)
764772
else:

tests/test_file.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,19 @@
2424

2525
import copy
2626
import datetime
27-
import httplib2
2827
import json
2928
import os
3029
import pickle
3130
import stat
3231
import tempfile
3332
import unittest
3433

35-
from oauth2client import GOOGLE_TOKEN_URI
34+
from .http_mock import HttpMockSequence
3635
from oauth2client import file
3736
from oauth2client import locked_file
3837
from oauth2client import multistore_file
3938
from oauth2client import util
4039
from oauth2client.client import AccessTokenCredentials
41-
from oauth2client.client import AssertionCredentials
4240
from oauth2client.client import OAuth2Credentials
4341
try:
4442
# Python2
@@ -64,11 +62,12 @@ def setUp(self):
6462
except OSError:
6563
pass
6664

67-
def create_test_credentials(self, client_id='some_client_id'):
65+
def create_test_credentials(self, client_id='some_client_id',
66+
expiration=None):
6867
access_token = 'foo'
6968
client_secret = 'cOuDdkfjxxnv+'
7069
refresh_token = '1/0/a.df219fjls0'
71-
token_expiry = datetime.datetime.utcnow()
70+
token_expiry = expiration or datetime.datetime.utcnow()
7271
token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
7372
user_agent = 'refresh_checker/1.0'
7473

@@ -119,8 +118,55 @@ def test_pickle_and_json_interop(self):
119118
self.assertEquals(data['_class'], 'OAuth2Credentials')
120119
self.assertEquals(data['_module'], OAuth2Credentials.__module__)
121120

122-
def test_token_refresh(self):
123-
credentials = self.create_test_credentials()
121+
def test_token_refresh_store_expired(self):
122+
expiration = datetime.datetime.utcnow() - datetime.timedelta(minutes=15)
123+
credentials = self.create_test_credentials(expiration=expiration)
124+
125+
s = file.Storage(FILENAME)
126+
s.put(credentials)
127+
credentials = s.get()
128+
new_cred = copy.copy(credentials)
129+
new_cred.access_token = 'bar'
130+
s.put(new_cred)
131+
132+
access_token = '1/3w'
133+
token_response = {'access_token': access_token, 'expires_in': 3600}
134+
http = HttpMockSequence([
135+
({'status': '200'}, json.dumps(token_response).encode('utf-8')),
136+
])
137+
138+
credentials._refresh(http.request)
139+
self.assertEquals(credentials.access_token, access_token)
140+
141+
def test_token_refresh_store_expires_soon(self):
142+
# Tests the case where an access token that is valid when it is read from
143+
# the store expires before the original request succeeds.
144+
expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
145+
credentials = self.create_test_credentials(expiration=expiration)
146+
147+
s = file.Storage(FILENAME)
148+
s.put(credentials)
149+
credentials = s.get()
150+
new_cred = copy.copy(credentials)
151+
new_cred.access_token = 'bar'
152+
s.put(new_cred)
153+
154+
access_token = '1/3w'
155+
token_response = {'access_token': access_token, 'expires_in': 3600}
156+
http = HttpMockSequence([
157+
({'status': '401'}, b'Initial token expired'),
158+
({'status': '401'}, b'Store token expired'),
159+
({'status': '200'}, json.dumps(token_response).encode('utf-8')),
160+
({'status': '200'}, b'Valid response to original request')
161+
])
162+
163+
credentials.authorize(http)
164+
http.request('https://example.com')
165+
self.assertEquals(credentials.access_token, access_token)
166+
167+
def test_token_refresh_good_store(self):
168+
expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
169+
credentials = self.create_test_credentials(expiration=expiration)
124170

125171
s = file.Storage(FILENAME)
126172
s.put(credentials)
@@ -287,7 +333,6 @@ def test_multistore_file_backwards_compatibility(self):
287333

288334
self.assertEqual(credentials.access_token, stored_credentials.access_token)
289335

290-
291336
def test_multistore_file_get_all_keys(self):
292337
# start with no keys
293338
keys = multistore_file.get_all_credential_keys(FILENAME)

0 commit comments

Comments
 (0)