Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d3a5cf4

Browse files
author
Jon Wayne Parrott
authored
Fix credentials usage in BatchHTTPRequest (googleapis#376)
1 parent 4ba8c23 commit d3a5cf4

File tree

4 files changed

+72
-24
lines changed

4 files changed

+72
-24
lines changed

googleapiclient/_auth.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
"""Helpers for authentication using oauth2client or google-auth."""
1616

17+
import httplib2
18+
1719
try:
1820
import google.auth
1921
import google.auth.credentials
@@ -29,8 +31,6 @@
2931
except ImportError: # pragma: NO COVER
3032
HAS_OAUTH2CLIENT = False
3133

32-
from googleapiclient.http import build_http
33-
3434

3535
def default_credentials():
3636
"""Returns Application Default Credentials."""
@@ -84,9 +84,49 @@ def authorized_http(credentials):
8484
Union[httplib2.Http, google_auth_httplib2.AuthorizedHttp]: An
8585
authorized http client.
8686
"""
87+
from googleapiclient.http import build_http
88+
8789
if HAS_GOOGLE_AUTH and isinstance(
8890
credentials, google.auth.credentials.Credentials):
8991
return google_auth_httplib2.AuthorizedHttp(credentials,
9092
http=build_http())
9193
else:
9294
return credentials.authorize(build_http())
95+
96+
97+
def refresh_credentials(credentials):
98+
# Refresh must use a new http instance, as the one associated with the
99+
# credentials could be a AuthorizedHttp or an oauth2client-decorated
100+
# Http instance which would cause a weird recursive loop of refreshing
101+
# and likely tear a hole in spacetime.
102+
refresh_http = httplib2.Http()
103+
if HAS_GOOGLE_AUTH and isinstance(
104+
credentials, google.auth.credentials.Credentials):
105+
request = google_auth_httplib2.Request(refresh_http)
106+
return credentials.refresh(request)
107+
else:
108+
return credentials.refresh(refresh_http)
109+
110+
111+
def apply_credentials(credentials, headers):
112+
# oauth2client and google-auth have the same interface for this.
113+
return credentials.apply(headers)
114+
115+
116+
def is_valid(credentials):
117+
if HAS_GOOGLE_AUTH and isinstance(
118+
credentials, google.auth.credentials.Credentials):
119+
return credentials.valid
120+
else:
121+
return not credentials.access_token_expired
122+
123+
124+
def get_credentials_from_http(http):
125+
if http is None:
126+
return None
127+
elif hasattr(http.request, 'credentials'):
128+
return http.request.credentials
129+
elif hasattr(http, 'credentials'):
130+
return http.credentials
131+
else:
132+
return None

googleapiclient/http.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
except ImportError:
6363
from oauth2client import _helpers as util
6464

65+
from googleapiclient import _auth
6566
from googleapiclient import mimeparse
6667
from googleapiclient.errors import BatchError
6768
from googleapiclient.errors import HttpError
@@ -1126,21 +1127,25 @@ def _refresh_and_apply_credentials(self, request, http):
11261127
# If there is no http per the request then refresh the http passed in
11271128
# via execute()
11281129
creds = None
1129-
if request.http is not None and hasattr(request.http.request,
1130-
'credentials'):
1131-
creds = request.http.request.credentials
1132-
elif http is not None and hasattr(http.request, 'credentials'):
1133-
creds = http.request.credentials
1130+
request_credentials = False
1131+
1132+
if request.http is not None:
1133+
creds = _auth.get_credentials_from_http(request.http)
1134+
request_credentials = True
1135+
1136+
if creds is None and http is not None:
1137+
creds = _auth.get_credentials_from_http(http)
1138+
11341139
if creds is not None:
11351140
if id(creds) not in self._refreshed_credentials:
1136-
creds.refresh(http)
1141+
_auth.refresh_credentials(creds)
11371142
self._refreshed_credentials[id(creds)] = 1
11381143

11391144
# Only apply the credentials if we are using the http object passed in,
11401145
# otherwise apply() will get called during _serialize_request().
1141-
if request.http is None or not hasattr(request.http.request,
1142-
'credentials'):
1143-
creds.apply(request.headers)
1146+
if request.http is None or not request_credentials:
1147+
_auth.apply_credentials(creds, request.headers)
1148+
11441149

11451150
def _id_to_header(self, id_):
11461151
"""Convert an id to a Content-ID header value.
@@ -1200,9 +1205,10 @@ def _serialize_request(self, request):
12001205
msg = MIMENonMultipart(major, minor)
12011206
headers = request.headers.copy()
12021207

1203-
if request.http is not None and hasattr(request.http.request,
1204-
'credentials'):
1205-
request.http.request.credentials.apply(headers)
1208+
if request.http is not None:
1209+
credentials = _auth.get_credentials_from_http(request.http)
1210+
if credentials is not None:
1211+
_auth.apply_credentials(credentials, headers)
12061212

12071213
# MIMENonMultipart adds its own Content-Type header.
12081214
if 'content-type' in headers:
@@ -1409,11 +1415,11 @@ def execute(self, http=None):
14091415

14101416
# Special case for OAuth2Credentials-style objects which have not yet been
14111417
# refreshed with an initial access_token.
1412-
if getattr(http.request, 'credentials', None) is not None:
1413-
creds = http.request.credentials
1414-
if not getattr(creds, 'access_token', None):
1418+
creds = _auth.get_credentials_from_http(http)
1419+
if creds is not None:
1420+
if not _auth.is_valid(creds):
14151421
LOGGER.info('Attempting refresh to obtain initial access_token')
1416-
creds.refresh(http)
1422+
_auth.refresh_credentials(creds)
14171423

14181424
self._execute(http, self._order, self._requests)
14191425

tests/test_http.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,17 @@
6262

6363
class MockCredentials(Credentials):
6464
"""Mock class for all Credentials objects."""
65-
def __init__(self, bearer_token):
65+
def __init__(self, bearer_token, expired=False):
6666
super(MockCredentials, self).__init__()
6767
self._authorized = 0
6868
self._refreshed = 0
6969
self._applied = 0
7070
self._bearer_token = bearer_token
71+
self._access_token_expired = expired
72+
73+
@property
74+
def access_token_expired(self):
75+
return self._access_token_expired
7176

7277
def authorize(self, http):
7378
self._authorized += 1
@@ -1128,10 +1133,7 @@ def test_execute_request_body(self):
11281133
def test_execute_initial_refresh_oauth2(self):
11291134
batch = BatchHttpRequest()
11301135
callbacks = Callbacks()
1131-
cred = MockCredentials('Foo')
1132-
1133-
# Pretend this is a OAuth2Credentials object
1134-
cred.access_token = None
1136+
cred = MockCredentials('Foo', expired=True)
11351137

11361138
http = HttpMockSequence([
11371139
({'status': '200',

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ deps =
1919
coverage>=3.6,<3.99
2020
unittest2
2121
mock
22-
commands = nosetests --with-coverage --cover-package=googleapiclient --nocapture --cover-erase --cover-tests --cover-branches --cover-min-percentage=85
22+
commands = nosetests --with-coverage --cover-package=googleapiclient --nocapture --cover-erase --cover-tests --cover-branches --cover-min-percentage=85 []

0 commit comments

Comments
 (0)