blob: 76aa463cbfd197788ecf72a1c56a1aadba0695d9 [file] [log] [blame]
C.J. Collier37141e42020-02-13 13:49:49 -08001# Copyright 2016 Google LLC
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -07002#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import datetime
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080016import json
17import os
Bu Sun Kim32d71a52019-12-18 11:30:46 -080018import pickle
19import sys
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070020
21import mock
Thea Flowers118c0482018-05-24 13:34:07 -070022import pytest
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070023
24from google.auth import _helpers
Thea Flowers118c0482018-05-24 13:34:07 -070025from google.auth import exceptions
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070026from google.auth import transport
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070027from google.oauth2 import credentials
28
29
Bu Sun Kim9eec0912019-10-21 17:04:21 -070030DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data")
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080031
Bu Sun Kim9eec0912019-10-21 17:04:21 -070032AUTH_USER_JSON_FILE = os.path.join(DATA_DIR, "authorized_user.json")
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080033
Bu Sun Kim9eec0912019-10-21 17:04:21 -070034with open(AUTH_USER_JSON_FILE, "r") as fh:
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080035 AUTH_USER_INFO = json.load(fh)
36
37
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070038class TestCredentials(object):
Bu Sun Kim9eec0912019-10-21 17:04:21 -070039 TOKEN_URI = "https://example.com/oauth2/token"
40 REFRESH_TOKEN = "refresh_token"
41 CLIENT_ID = "client_id"
42 CLIENT_SECRET = "client_secret"
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070043
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070044 @classmethod
45 def make_credentials(cls):
46 return credentials.Credentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -070047 token=None,
48 refresh_token=cls.REFRESH_TOKEN,
49 token_uri=cls.TOKEN_URI,
50 client_id=cls.CLIENT_ID,
51 client_secret=cls.CLIENT_SECRET,
52 )
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070053
54 def test_default_state(self):
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070055 credentials = self.make_credentials()
56 assert not credentials.valid
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070057 # Expiration hasn't been set yet
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070058 assert not credentials.expired
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070059 # Scopes aren't required for these credentials
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070060 assert not credentials.requires_scopes
Jon Wayne Parrott2d0549a2017-03-01 09:27:16 -080061 # Test properties
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070062 assert credentials.refresh_token == self.REFRESH_TOKEN
63 assert credentials.token_uri == self.TOKEN_URI
64 assert credentials.client_id == self.CLIENT_ID
65 assert credentials.client_secret == self.CLIENT_SECRET
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070066
Bu Sun Kim9eec0912019-10-21 17:04:21 -070067 @mock.patch("google.oauth2._client.refresh_grant", autospec=True)
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070068 @mock.patch(
Bu Sun Kim9eec0912019-10-21 17:04:21 -070069 "google.auth._helpers.utcnow",
70 return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
71 )
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070072 def test_refresh_success(self, unused_utcnow, refresh_grant):
Bu Sun Kim9eec0912019-10-21 17:04:21 -070073 token = "token"
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070074 expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
Bu Sun Kim9eec0912019-10-21 17:04:21 -070075 grant_response = {"id_token": mock.sentinel.id_token}
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070076 refresh_grant.return_value = (
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070077 # Access token
78 token,
79 # New refresh token
80 None,
81 # Expiry,
82 expiry,
83 # Extra data
Bu Sun Kim9eec0912019-10-21 17:04:21 -070084 grant_response,
85 )
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070086
87 request = mock.create_autospec(transport.Request)
88 credentials = self.make_credentials()
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070089
90 # Refresh credentials
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070091 credentials.refresh(request)
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070092
93 # Check jwt grant call.
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -070094 refresh_grant.assert_called_with(
Bu Sun Kim9eec0912019-10-21 17:04:21 -070095 request,
96 self.TOKEN_URI,
97 self.REFRESH_TOKEN,
98 self.CLIENT_ID,
99 self.CLIENT_SECRET,
100 None,
101 )
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700102
103 # Check that the credentials have the token and expiry
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -0700104 assert credentials.token == token
105 assert credentials.expiry == expiry
106 assert credentials.id_token == mock.sentinel.id_token
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700107
108 # Check that the credentials are valid (have a token and are not
109 # expired)
Jon Wayne Parrott78fec2c2017-06-30 10:25:08 -0700110 assert credentials.valid
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800111
Thea Flowers118c0482018-05-24 13:34:07 -0700112 def test_refresh_no_refresh_token(self):
113 request = mock.create_autospec(transport.Request)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700114 credentials_ = credentials.Credentials(token=None, refresh_token=None)
Thea Flowers118c0482018-05-24 13:34:07 -0700115
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700116 with pytest.raises(exceptions.RefreshError, match="necessary fields"):
Thea Flowers118c0482018-05-24 13:34:07 -0700117 credentials_.refresh(request)
118
119 request.assert_not_called()
120
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700121 @mock.patch("google.oauth2._client.refresh_grant", autospec=True)
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400122 @mock.patch(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700123 "google.auth._helpers.utcnow",
124 return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
125 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400126 def test_credentials_with_scopes_requested_refresh_success(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700127 self, unused_utcnow, refresh_grant
128 ):
129 scopes = ["email", "profile"]
130 token = "token"
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400131 expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700132 grant_response = {"id_token": mock.sentinel.id_token}
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400133 refresh_grant.return_value = (
134 # Access token
135 token,
136 # New refresh token
137 None,
138 # Expiry,
139 expiry,
140 # Extra data
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700141 grant_response,
142 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400143
144 request = mock.create_autospec(transport.Request)
145 creds = credentials.Credentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700146 token=None,
147 refresh_token=self.REFRESH_TOKEN,
148 token_uri=self.TOKEN_URI,
149 client_id=self.CLIENT_ID,
150 client_secret=self.CLIENT_SECRET,
151 scopes=scopes,
152 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400153
154 # Refresh credentials
155 creds.refresh(request)
156
157 # Check jwt grant call.
158 refresh_grant.assert_called_with(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700159 request,
160 self.TOKEN_URI,
161 self.REFRESH_TOKEN,
162 self.CLIENT_ID,
163 self.CLIENT_SECRET,
164 scopes,
165 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400166
167 # Check that the credentials have the token and expiry
168 assert creds.token == token
169 assert creds.expiry == expiry
170 assert creds.id_token == mock.sentinel.id_token
171 assert creds.has_scopes(scopes)
172
173 # Check that the credentials are valid (have a token and are not
174 # expired.)
175 assert creds.valid
176
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700177 @mock.patch("google.oauth2._client.refresh_grant", autospec=True)
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400178 @mock.patch(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700179 "google.auth._helpers.utcnow",
180 return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
181 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400182 def test_credentials_with_scopes_returned_refresh_success(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700183 self, unused_utcnow, refresh_grant
184 ):
185 scopes = ["email", "profile"]
186 token = "token"
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400187 expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700188 grant_response = {
189 "id_token": mock.sentinel.id_token,
190 "scopes": " ".join(scopes),
191 }
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400192 refresh_grant.return_value = (
193 # Access token
194 token,
195 # New refresh token
196 None,
197 # Expiry,
198 expiry,
199 # Extra data
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700200 grant_response,
201 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400202
203 request = mock.create_autospec(transport.Request)
204 creds = credentials.Credentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700205 token=None,
206 refresh_token=self.REFRESH_TOKEN,
207 token_uri=self.TOKEN_URI,
208 client_id=self.CLIENT_ID,
209 client_secret=self.CLIENT_SECRET,
210 scopes=scopes,
211 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400212
213 # Refresh credentials
214 creds.refresh(request)
215
216 # Check jwt grant call.
217 refresh_grant.assert_called_with(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700218 request,
219 self.TOKEN_URI,
220 self.REFRESH_TOKEN,
221 self.CLIENT_ID,
222 self.CLIENT_SECRET,
223 scopes,
224 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400225
226 # Check that the credentials have the token and expiry
227 assert creds.token == token
228 assert creds.expiry == expiry
229 assert creds.id_token == mock.sentinel.id_token
230 assert creds.has_scopes(scopes)
231
232 # Check that the credentials are valid (have a token and are not
233 # expired.)
234 assert creds.valid
235
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700236 @mock.patch("google.oauth2._client.refresh_grant", autospec=True)
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400237 @mock.patch(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700238 "google.auth._helpers.utcnow",
239 return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
240 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400241 def test_credentials_with_scopes_refresh_failure_raises_refresh_error(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700242 self, unused_utcnow, refresh_grant
243 ):
244 scopes = ["email", "profile"]
245 scopes_returned = ["email"]
246 token = "token"
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400247 expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700248 grant_response = {
249 "id_token": mock.sentinel.id_token,
250 "scopes": " ".join(scopes_returned),
251 }
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400252 refresh_grant.return_value = (
253 # Access token
254 token,
255 # New refresh token
256 None,
257 # Expiry,
258 expiry,
259 # Extra data
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700260 grant_response,
261 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400262
263 request = mock.create_autospec(transport.Request)
264 creds = credentials.Credentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700265 token=None,
266 refresh_token=self.REFRESH_TOKEN,
267 token_uri=self.TOKEN_URI,
268 client_id=self.CLIENT_ID,
269 client_secret=self.CLIENT_SECRET,
270 scopes=scopes,
271 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400272
273 # Refresh credentials
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700274 with pytest.raises(
275 exceptions.RefreshError, match="Not all requested scopes were granted"
276 ):
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400277 creds.refresh(request)
278
279 # Check jwt grant call.
280 refresh_grant.assert_called_with(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700281 request,
282 self.TOKEN_URI,
283 self.REFRESH_TOKEN,
284 self.CLIENT_ID,
285 self.CLIENT_SECRET,
286 scopes,
287 )
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400288
289 # Check that the credentials have the token and expiry
290 assert creds.token == token
291 assert creds.expiry == expiry
292 assert creds.id_token == mock.sentinel.id_token
293 assert creds.has_scopes(scopes)
294
295 # Check that the credentials are valid (have a token and are not
296 # expired.)
297 assert creds.valid
298
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800299 def test_apply_with_quota_project_id(self):
300 creds = credentials.Credentials(
301 token="token",
302 refresh_token=self.REFRESH_TOKEN,
303 token_uri=self.TOKEN_URI,
304 client_id=self.CLIENT_ID,
305 client_secret=self.CLIENT_SECRET,
306 quota_project_id="quota-project-123",
307 )
308
309 headers = {}
310 creds.apply(headers)
311 assert headers["x-goog-user-project"] == "quota-project-123"
312
313 def test_apply_with_no_quota_project_id(self):
314 creds = credentials.Credentials(
315 token="token",
316 refresh_token=self.REFRESH_TOKEN,
317 token_uri=self.TOKEN_URI,
318 client_id=self.CLIENT_ID,
319 client_secret=self.CLIENT_SECRET,
320 )
321
322 headers = {}
323 creds.apply(headers)
324 assert "x-goog-user-project" not in headers
325
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800326 def test_from_authorized_user_info(self):
327 info = AUTH_USER_INFO.copy()
328
329 creds = credentials.Credentials.from_authorized_user_info(info)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700330 assert creds.client_secret == info["client_secret"]
331 assert creds.client_id == info["client_id"]
332 assert creds.refresh_token == info["refresh_token"]
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800333 assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
334 assert creds.scopes is None
335
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700336 scopes = ["email", "profile"]
337 creds = credentials.Credentials.from_authorized_user_info(info, scopes)
338 assert creds.client_secret == info["client_secret"]
339 assert creds.client_id == info["client_id"]
340 assert creds.refresh_token == info["refresh_token"]
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800341 assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
342 assert creds.scopes == scopes
343
344 def test_from_authorized_user_file(self):
345 info = AUTH_USER_INFO.copy()
346
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700347 creds = credentials.Credentials.from_authorized_user_file(AUTH_USER_JSON_FILE)
348 assert creds.client_secret == info["client_secret"]
349 assert creds.client_id == info["client_id"]
350 assert creds.refresh_token == info["refresh_token"]
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800351 assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
352 assert creds.scopes is None
353
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700354 scopes = ["email", "profile"]
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800355 creds = credentials.Credentials.from_authorized_user_file(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700356 AUTH_USER_JSON_FILE, scopes
357 )
358 assert creds.client_secret == info["client_secret"]
359 assert creds.client_id == info["client_id"]
360 assert creds.refresh_token == info["refresh_token"]
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800361 assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
362 assert creds.scopes == scopes
patkasperbfb1f8c2019-12-05 22:03:44 +0100363
364 def test_to_json(self):
365 info = AUTH_USER_INFO.copy()
366 creds = credentials.Credentials.from_authorized_user_info(info)
367
368 # Test with no `strip` arg
369 json_output = creds.to_json()
370 json_asdict = json.loads(json_output)
371 assert json_asdict.get("token") == creds.token
372 assert json_asdict.get("refresh_token") == creds.refresh_token
373 assert json_asdict.get("token_uri") == creds.token_uri
374 assert json_asdict.get("client_id") == creds.client_id
375 assert json_asdict.get("scopes") == creds.scopes
376 assert json_asdict.get("client_secret") == creds.client_secret
377
378 # Test with a `strip` arg
379 json_output = creds.to_json(strip=["client_secret"])
380 json_asdict = json.loads(json_output)
381 assert json_asdict.get("token") == creds.token
382 assert json_asdict.get("refresh_token") == creds.refresh_token
383 assert json_asdict.get("token_uri") == creds.token_uri
384 assert json_asdict.get("client_id") == creds.client_id
385 assert json_asdict.get("scopes") == creds.scopes
386 assert json_asdict.get("client_secret") is None
Bu Sun Kim32d71a52019-12-18 11:30:46 -0800387
388 def test_pickle_and_unpickle(self):
389 creds = self.make_credentials()
390 unpickled = pickle.loads(pickle.dumps(creds))
391
392 # make sure attributes aren't lost during pickling
393 assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort()
394
395 for attr in list(creds.__dict__):
396 assert getattr(creds, attr) == getattr(unpickled, attr)
397
398 def test_pickle_with_missing_attribute(self):
399 creds = self.make_credentials()
400
401 # remove an optional attribute before pickling
402 # this mimics a pickle created with a previous class definition with
403 # fewer attributes
404 del creds.__dict__["_quota_project_id"]
405
406 unpickled = pickle.loads(pickle.dumps(creds))
407
408 # Attribute should be initialized by `__setstate__`
409 assert unpickled.quota_project_id is None
410
411 # pickles are not compatible across versions
412 @pytest.mark.skipif(
413 sys.version_info < (3, 5),
414 reason="pickle file can only be loaded with Python >= 3.5",
415 )
416 def test_unpickle_old_credentials_pickle(self):
417 # make sure a credentials file pickled with an older
418 # library version (google-auth==1.5.1) can be unpickled
419 with open(
420 os.path.join(DATA_DIR, "old_oauth_credentials_py3.pickle"), "rb"
421 ) as f:
422 credentials = pickle.load(f)
423 assert credentials.quota_project_id is None
arithmetic1728772dac62020-03-27 14:34:13 -0700424
425
426class TestUserAccessTokenCredentials(object):
427 def test_instance(self):
428 cred = credentials.UserAccessTokenCredentials()
429 assert cred._account is None
430
431 cred = cred.with_account("account")
432 assert cred._account == "account"
433
434 @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True)
435 def test_refresh(self, get_auth_access_token):
436 get_auth_access_token.return_value = "access_token"
437 cred = credentials.UserAccessTokenCredentials()
438 cred.refresh(None)
439 assert cred.token == "access_token"
440
441 @mock.patch(
442 "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True
443 )
444 @mock.patch(
445 "google.oauth2.credentials.UserAccessTokenCredentials.refresh", autospec=True
446 )
447 def test_before_request(self, refresh, apply):
448 cred = credentials.UserAccessTokenCredentials()
449 cred.before_request(mock.Mock(), "GET", "https://example.com", {})
450 refresh.assert_called()
451 apply.assert_called()