test_credentials.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. # Copyright 2016 Google LLC
  2. #
  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. import datetime
  15. import json
  16. import os
  17. import pickle
  18. import sys
  19. import mock
  20. import pytest # type: ignore
  21. from google.auth import _helpers
  22. from google.auth import exceptions
  23. from google.auth import transport
  24. from google.auth.credentials import TokenState
  25. from google.oauth2 import credentials
  26. import yatest.common as yc
  27. DATA_DIR = os.path.join(os.path.dirname(yc.source_path(__file__)), "..", "data")
  28. AUTH_USER_JSON_FILE = os.path.join(DATA_DIR, "authorized_user.json")
  29. with open(AUTH_USER_JSON_FILE, "r") as fh:
  30. AUTH_USER_INFO = json.load(fh)
  31. class TestCredentials(object):
  32. TOKEN_URI = "https://example.com/oauth2/token"
  33. REFRESH_TOKEN = "refresh_token"
  34. RAPT_TOKEN = "rapt_token"
  35. CLIENT_ID = "client_id"
  36. CLIENT_SECRET = "client_secret"
  37. @classmethod
  38. def make_credentials(cls):
  39. return credentials.Credentials(
  40. token=None,
  41. refresh_token=cls.REFRESH_TOKEN,
  42. token_uri=cls.TOKEN_URI,
  43. client_id=cls.CLIENT_ID,
  44. client_secret=cls.CLIENT_SECRET,
  45. rapt_token=cls.RAPT_TOKEN,
  46. enable_reauth_refresh=True,
  47. )
  48. def test_default_state(self):
  49. credentials = self.make_credentials()
  50. assert not credentials.valid
  51. # Expiration hasn't been set yet
  52. assert not credentials.expired
  53. # Scopes aren't required for these credentials
  54. assert not credentials.requires_scopes
  55. assert credentials.token_state == TokenState.INVALID
  56. # Test properties
  57. assert credentials.refresh_token == self.REFRESH_TOKEN
  58. assert credentials.token_uri == self.TOKEN_URI
  59. assert credentials.client_id == self.CLIENT_ID
  60. assert credentials.client_secret == self.CLIENT_SECRET
  61. assert credentials.rapt_token == self.RAPT_TOKEN
  62. assert credentials.refresh_handler is None
  63. def test_get_cred_info(self):
  64. credentials = self.make_credentials()
  65. credentials._account = "fake-account"
  66. assert not credentials.get_cred_info()
  67. credentials._cred_file_path = "/path/to/file"
  68. assert credentials.get_cred_info() == {
  69. "credential_source": "/path/to/file",
  70. "credential_type": "user credentials",
  71. "principal": "fake-account",
  72. }
  73. def test_get_cred_info_no_account(self):
  74. credentials = self.make_credentials()
  75. assert not credentials.get_cred_info()
  76. credentials._cred_file_path = "/path/to/file"
  77. assert credentials.get_cred_info() == {
  78. "credential_source": "/path/to/file",
  79. "credential_type": "user credentials",
  80. }
  81. def test__make_copy_get_cred_info(self):
  82. credentials = self.make_credentials()
  83. credentials._cred_file_path = "/path/to/file"
  84. cred_copy = credentials._make_copy()
  85. assert cred_copy._cred_file_path == "/path/to/file"
  86. def test_token_usage_metrics(self):
  87. credentials = self.make_credentials()
  88. credentials.token = "token"
  89. credentials.expiry = None
  90. headers = {}
  91. credentials.before_request(mock.Mock(), None, None, headers)
  92. assert headers["authorization"] == "Bearer token"
  93. assert headers["x-goog-api-client"] == "cred-type/u"
  94. def test_refresh_handler_setter_and_getter(self):
  95. scopes = ["email", "profile"]
  96. original_refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN_1", None))
  97. updated_refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN_2", None))
  98. creds = credentials.Credentials(
  99. token=None,
  100. refresh_token=None,
  101. token_uri=None,
  102. client_id=None,
  103. client_secret=None,
  104. rapt_token=None,
  105. scopes=scopes,
  106. default_scopes=None,
  107. refresh_handler=original_refresh_handler,
  108. )
  109. assert creds.refresh_handler is original_refresh_handler
  110. creds.refresh_handler = updated_refresh_handler
  111. assert creds.refresh_handler is updated_refresh_handler
  112. creds.refresh_handler = None
  113. assert creds.refresh_handler is None
  114. def test_invalid_refresh_handler(self):
  115. scopes = ["email", "profile"]
  116. with pytest.raises(TypeError) as excinfo:
  117. credentials.Credentials(
  118. token=None,
  119. refresh_token=None,
  120. token_uri=None,
  121. client_id=None,
  122. client_secret=None,
  123. rapt_token=None,
  124. scopes=scopes,
  125. default_scopes=None,
  126. refresh_handler=object(),
  127. )
  128. assert excinfo.match("The provided refresh_handler is not a callable or None.")
  129. def test_refresh_with_non_default_universe_domain(self):
  130. creds = credentials.Credentials(
  131. token="token", universe_domain="dummy_universe.com"
  132. )
  133. with pytest.raises(exceptions.RefreshError) as excinfo:
  134. creds.refresh(mock.Mock())
  135. assert excinfo.match(
  136. "refresh is only supported in the default googleapis.com universe domain"
  137. )
  138. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  139. @mock.patch(
  140. "google.auth._helpers.utcnow",
  141. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  142. )
  143. def test_refresh_success(self, unused_utcnow, refresh_grant):
  144. token = "token"
  145. new_rapt_token = "new_rapt_token"
  146. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  147. grant_response = {"id_token": mock.sentinel.id_token}
  148. refresh_grant.return_value = (
  149. # Access token
  150. token,
  151. # New refresh token
  152. None,
  153. # Expiry,
  154. expiry,
  155. # Extra data
  156. grant_response,
  157. # rapt_token
  158. new_rapt_token,
  159. )
  160. request = mock.create_autospec(transport.Request)
  161. credentials = self.make_credentials()
  162. # Refresh credentials
  163. credentials.refresh(request)
  164. # Check jwt grant call.
  165. refresh_grant.assert_called_with(
  166. request,
  167. self.TOKEN_URI,
  168. self.REFRESH_TOKEN,
  169. self.CLIENT_ID,
  170. self.CLIENT_SECRET,
  171. None,
  172. self.RAPT_TOKEN,
  173. True,
  174. )
  175. # Check that the credentials have the token and expiry
  176. assert credentials.token == token
  177. assert credentials.expiry == expiry
  178. assert credentials.id_token == mock.sentinel.id_token
  179. assert credentials.rapt_token == new_rapt_token
  180. # Check that the credentials are valid (have a token and are not
  181. # expired)
  182. assert credentials.valid
  183. def test_refresh_no_refresh_token(self):
  184. request = mock.create_autospec(transport.Request)
  185. credentials_ = credentials.Credentials(token=None, refresh_token=None)
  186. with pytest.raises(exceptions.RefreshError, match="necessary fields"):
  187. credentials_.refresh(request)
  188. request.assert_not_called()
  189. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  190. @mock.patch(
  191. "google.auth._helpers.utcnow",
  192. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  193. )
  194. def test_refresh_with_refresh_token_and_refresh_handler(
  195. self, unused_utcnow, refresh_grant
  196. ):
  197. token = "token"
  198. new_rapt_token = "new_rapt_token"
  199. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  200. grant_response = {"id_token": mock.sentinel.id_token}
  201. refresh_grant.return_value = (
  202. # Access token
  203. token,
  204. # New refresh token
  205. None,
  206. # Expiry,
  207. expiry,
  208. # Extra data
  209. grant_response,
  210. # rapt_token
  211. new_rapt_token,
  212. )
  213. refresh_handler = mock.Mock()
  214. request = mock.create_autospec(transport.Request)
  215. creds = credentials.Credentials(
  216. token=None,
  217. refresh_token=self.REFRESH_TOKEN,
  218. token_uri=self.TOKEN_URI,
  219. client_id=self.CLIENT_ID,
  220. client_secret=self.CLIENT_SECRET,
  221. rapt_token=self.RAPT_TOKEN,
  222. refresh_handler=refresh_handler,
  223. )
  224. # Refresh credentials
  225. creds.refresh(request)
  226. # Check jwt grant call.
  227. refresh_grant.assert_called_with(
  228. request,
  229. self.TOKEN_URI,
  230. self.REFRESH_TOKEN,
  231. self.CLIENT_ID,
  232. self.CLIENT_SECRET,
  233. None,
  234. self.RAPT_TOKEN,
  235. False,
  236. )
  237. # Check that the credentials have the token and expiry
  238. assert creds.token == token
  239. assert creds.expiry == expiry
  240. assert creds.id_token == mock.sentinel.id_token
  241. assert creds.rapt_token == new_rapt_token
  242. # Check that the credentials are valid (have a token and are not
  243. # expired)
  244. assert creds.valid
  245. # Assert refresh handler not called as the refresh token has
  246. # higher priority.
  247. refresh_handler.assert_not_called()
  248. @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
  249. def test_refresh_with_refresh_handler_success_scopes(self, unused_utcnow):
  250. expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800)
  251. refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN", expected_expiry))
  252. scopes = ["email", "profile"]
  253. default_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  254. request = mock.create_autospec(transport.Request)
  255. creds = credentials.Credentials(
  256. token=None,
  257. refresh_token=None,
  258. token_uri=None,
  259. client_id=None,
  260. client_secret=None,
  261. rapt_token=None,
  262. scopes=scopes,
  263. default_scopes=default_scopes,
  264. refresh_handler=refresh_handler,
  265. )
  266. creds.refresh(request)
  267. assert creds.token == "ACCESS_TOKEN"
  268. assert creds.expiry == expected_expiry
  269. assert creds.valid
  270. assert not creds.expired
  271. # Confirm refresh handler called with the expected arguments.
  272. refresh_handler.assert_called_with(request, scopes=scopes)
  273. @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
  274. def test_refresh_with_refresh_handler_success_default_scopes(self, unused_utcnow):
  275. expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800)
  276. original_refresh_handler = mock.Mock(
  277. return_value=("UNUSED_TOKEN", expected_expiry)
  278. )
  279. refresh_handler = mock.Mock(return_value=("ACCESS_TOKEN", expected_expiry))
  280. default_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  281. request = mock.create_autospec(transport.Request)
  282. creds = credentials.Credentials(
  283. token=None,
  284. refresh_token=None,
  285. token_uri=None,
  286. client_id=None,
  287. client_secret=None,
  288. rapt_token=None,
  289. scopes=None,
  290. default_scopes=default_scopes,
  291. refresh_handler=original_refresh_handler,
  292. )
  293. # Test newly set refresh_handler is used instead of the original one.
  294. creds.refresh_handler = refresh_handler
  295. creds.refresh(request)
  296. assert creds.token == "ACCESS_TOKEN"
  297. assert creds.expiry == expected_expiry
  298. assert creds.valid
  299. assert not creds.expired
  300. # default_scopes should be used since no developer provided scopes
  301. # are provided.
  302. refresh_handler.assert_called_with(request, scopes=default_scopes)
  303. @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
  304. def test_refresh_with_refresh_handler_invalid_token(self, unused_utcnow):
  305. expected_expiry = datetime.datetime.min + datetime.timedelta(seconds=2800)
  306. # Simulate refresh handler does not return a valid token.
  307. refresh_handler = mock.Mock(return_value=(None, expected_expiry))
  308. scopes = ["email", "profile"]
  309. default_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  310. request = mock.create_autospec(transport.Request)
  311. creds = credentials.Credentials(
  312. token=None,
  313. refresh_token=None,
  314. token_uri=None,
  315. client_id=None,
  316. client_secret=None,
  317. rapt_token=None,
  318. scopes=scopes,
  319. default_scopes=default_scopes,
  320. refresh_handler=refresh_handler,
  321. )
  322. with pytest.raises(
  323. exceptions.RefreshError, match="returned token is not a string"
  324. ):
  325. creds.refresh(request)
  326. assert creds.token is None
  327. assert creds.expiry is None
  328. assert not creds.valid
  329. # Confirm refresh handler called with the expected arguments.
  330. refresh_handler.assert_called_with(request, scopes=scopes)
  331. def test_refresh_with_refresh_handler_invalid_expiry(self):
  332. # Simulate refresh handler returns expiration time in an invalid unit.
  333. refresh_handler = mock.Mock(return_value=("TOKEN", 2800))
  334. scopes = ["email", "profile"]
  335. default_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  336. request = mock.create_autospec(transport.Request)
  337. creds = credentials.Credentials(
  338. token=None,
  339. refresh_token=None,
  340. token_uri=None,
  341. client_id=None,
  342. client_secret=None,
  343. rapt_token=None,
  344. scopes=scopes,
  345. default_scopes=default_scopes,
  346. refresh_handler=refresh_handler,
  347. )
  348. with pytest.raises(
  349. exceptions.RefreshError, match="returned expiry is not a datetime object"
  350. ):
  351. creds.refresh(request)
  352. assert creds.token is None
  353. assert creds.expiry is None
  354. assert not creds.valid
  355. # Confirm refresh handler called with the expected arguments.
  356. refresh_handler.assert_called_with(request, scopes=scopes)
  357. @mock.patch("google.auth._helpers.utcnow", return_value=datetime.datetime.min)
  358. def test_refresh_with_refresh_handler_expired_token(self, unused_utcnow):
  359. expected_expiry = datetime.datetime.min + _helpers.REFRESH_THRESHOLD
  360. # Simulate refresh handler returns an expired token.
  361. refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry))
  362. scopes = ["email", "profile"]
  363. default_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  364. request = mock.create_autospec(transport.Request)
  365. creds = credentials.Credentials(
  366. token=None,
  367. refresh_token=None,
  368. token_uri=None,
  369. client_id=None,
  370. client_secret=None,
  371. rapt_token=None,
  372. scopes=scopes,
  373. default_scopes=default_scopes,
  374. refresh_handler=refresh_handler,
  375. )
  376. with pytest.raises(exceptions.RefreshError, match="already expired"):
  377. creds.refresh(request)
  378. assert creds.token is None
  379. assert creds.expiry is None
  380. assert not creds.valid
  381. # Confirm refresh handler called with the expected arguments.
  382. refresh_handler.assert_called_with(request, scopes=scopes)
  383. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  384. @mock.patch(
  385. "google.auth._helpers.utcnow",
  386. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  387. )
  388. def test_credentials_with_scopes_requested_refresh_success(
  389. self, unused_utcnow, refresh_grant
  390. ):
  391. scopes = ["email", "profile"]
  392. default_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  393. token = "token"
  394. new_rapt_token = "new_rapt_token"
  395. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  396. grant_response = {"id_token": mock.sentinel.id_token, "scope": "email profile"}
  397. refresh_grant.return_value = (
  398. # Access token
  399. token,
  400. # New refresh token
  401. None,
  402. # Expiry,
  403. expiry,
  404. # Extra data
  405. grant_response,
  406. # rapt token
  407. new_rapt_token,
  408. )
  409. request = mock.create_autospec(transport.Request)
  410. creds = credentials.Credentials(
  411. token=None,
  412. refresh_token=self.REFRESH_TOKEN,
  413. token_uri=self.TOKEN_URI,
  414. client_id=self.CLIENT_ID,
  415. client_secret=self.CLIENT_SECRET,
  416. scopes=scopes,
  417. default_scopes=default_scopes,
  418. rapt_token=self.RAPT_TOKEN,
  419. enable_reauth_refresh=True,
  420. )
  421. # Refresh credentials
  422. creds.refresh(request)
  423. # Check jwt grant call.
  424. refresh_grant.assert_called_with(
  425. request,
  426. self.TOKEN_URI,
  427. self.REFRESH_TOKEN,
  428. self.CLIENT_ID,
  429. self.CLIENT_SECRET,
  430. scopes,
  431. self.RAPT_TOKEN,
  432. True,
  433. )
  434. # Check that the credentials have the token and expiry
  435. assert creds.token == token
  436. assert creds.expiry == expiry
  437. assert creds.id_token == mock.sentinel.id_token
  438. assert creds.has_scopes(scopes)
  439. assert creds.rapt_token == new_rapt_token
  440. assert creds.granted_scopes == scopes
  441. # Check that the credentials are valid (have a token and are not
  442. # expired.)
  443. assert creds.valid
  444. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  445. @mock.patch(
  446. "google.auth._helpers.utcnow",
  447. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  448. )
  449. def test_credentials_with_only_default_scopes_requested(
  450. self, unused_utcnow, refresh_grant
  451. ):
  452. default_scopes = ["email", "profile"]
  453. token = "token"
  454. new_rapt_token = "new_rapt_token"
  455. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  456. grant_response = {"id_token": mock.sentinel.id_token, "scope": "email profile"}
  457. refresh_grant.return_value = (
  458. # Access token
  459. token,
  460. # New refresh token
  461. None,
  462. # Expiry,
  463. expiry,
  464. # Extra data
  465. grant_response,
  466. # rapt token
  467. new_rapt_token,
  468. )
  469. request = mock.create_autospec(transport.Request)
  470. creds = credentials.Credentials(
  471. token=None,
  472. refresh_token=self.REFRESH_TOKEN,
  473. token_uri=self.TOKEN_URI,
  474. client_id=self.CLIENT_ID,
  475. client_secret=self.CLIENT_SECRET,
  476. default_scopes=default_scopes,
  477. rapt_token=self.RAPT_TOKEN,
  478. enable_reauth_refresh=True,
  479. )
  480. # Refresh credentials
  481. creds.refresh(request)
  482. # Check jwt grant call.
  483. refresh_grant.assert_called_with(
  484. request,
  485. self.TOKEN_URI,
  486. self.REFRESH_TOKEN,
  487. self.CLIENT_ID,
  488. self.CLIENT_SECRET,
  489. default_scopes,
  490. self.RAPT_TOKEN,
  491. True,
  492. )
  493. # Check that the credentials have the token and expiry
  494. assert creds.token == token
  495. assert creds.expiry == expiry
  496. assert creds.id_token == mock.sentinel.id_token
  497. assert creds.has_scopes(default_scopes)
  498. assert creds.rapt_token == new_rapt_token
  499. assert creds.granted_scopes == default_scopes
  500. # Check that the credentials are valid (have a token and are not
  501. # expired.)
  502. assert creds.valid
  503. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  504. @mock.patch(
  505. "google.auth._helpers.utcnow",
  506. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  507. )
  508. def test_credentials_with_scopes_returned_refresh_success(
  509. self, unused_utcnow, refresh_grant
  510. ):
  511. scopes = ["email", "profile"]
  512. token = "token"
  513. new_rapt_token = "new_rapt_token"
  514. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  515. grant_response = {"id_token": mock.sentinel.id_token, "scope": " ".join(scopes)}
  516. refresh_grant.return_value = (
  517. # Access token
  518. token,
  519. # New refresh token
  520. None,
  521. # Expiry,
  522. expiry,
  523. # Extra data
  524. grant_response,
  525. # rapt token
  526. new_rapt_token,
  527. )
  528. request = mock.create_autospec(transport.Request)
  529. creds = credentials.Credentials(
  530. token=None,
  531. refresh_token=self.REFRESH_TOKEN,
  532. token_uri=self.TOKEN_URI,
  533. client_id=self.CLIENT_ID,
  534. client_secret=self.CLIENT_SECRET,
  535. scopes=scopes,
  536. rapt_token=self.RAPT_TOKEN,
  537. enable_reauth_refresh=True,
  538. )
  539. # Refresh credentials
  540. creds.refresh(request)
  541. # Check jwt grant call.
  542. refresh_grant.assert_called_with(
  543. request,
  544. self.TOKEN_URI,
  545. self.REFRESH_TOKEN,
  546. self.CLIENT_ID,
  547. self.CLIENT_SECRET,
  548. scopes,
  549. self.RAPT_TOKEN,
  550. True,
  551. )
  552. # Check that the credentials have the token and expiry
  553. assert creds.token == token
  554. assert creds.expiry == expiry
  555. assert creds.id_token == mock.sentinel.id_token
  556. assert creds.has_scopes(scopes)
  557. assert creds.rapt_token == new_rapt_token
  558. assert creds.granted_scopes == scopes
  559. # Check that the credentials are valid (have a token and are not
  560. # expired.)
  561. assert creds.valid
  562. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  563. @mock.patch(
  564. "google.auth._helpers.utcnow",
  565. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  566. )
  567. def test_credentials_with_only_default_scopes_requested_different_granted_scopes(
  568. self, unused_utcnow, refresh_grant
  569. ):
  570. default_scopes = ["email", "profile"]
  571. token = "token"
  572. new_rapt_token = "new_rapt_token"
  573. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  574. grant_response = {"id_token": mock.sentinel.id_token, "scope": "email"}
  575. refresh_grant.return_value = (
  576. # Access token
  577. token,
  578. # New refresh token
  579. None,
  580. # Expiry,
  581. expiry,
  582. # Extra data
  583. grant_response,
  584. # rapt token
  585. new_rapt_token,
  586. )
  587. request = mock.create_autospec(transport.Request)
  588. creds = credentials.Credentials(
  589. token=None,
  590. refresh_token=self.REFRESH_TOKEN,
  591. token_uri=self.TOKEN_URI,
  592. client_id=self.CLIENT_ID,
  593. client_secret=self.CLIENT_SECRET,
  594. default_scopes=default_scopes,
  595. rapt_token=self.RAPT_TOKEN,
  596. enable_reauth_refresh=True,
  597. )
  598. # Refresh credentials
  599. creds.refresh(request)
  600. # Check jwt grant call.
  601. refresh_grant.assert_called_with(
  602. request,
  603. self.TOKEN_URI,
  604. self.REFRESH_TOKEN,
  605. self.CLIENT_ID,
  606. self.CLIENT_SECRET,
  607. default_scopes,
  608. self.RAPT_TOKEN,
  609. True,
  610. )
  611. # Check that the credentials have the token and expiry
  612. assert creds.token == token
  613. assert creds.expiry == expiry
  614. assert creds.id_token == mock.sentinel.id_token
  615. assert creds.has_scopes(default_scopes)
  616. assert creds.rapt_token == new_rapt_token
  617. assert creds.granted_scopes == ["email"]
  618. # Check that the credentials are valid (have a token and are not
  619. # expired.)
  620. assert creds.valid
  621. @mock.patch("google.oauth2.reauth.refresh_grant", autospec=True)
  622. @mock.patch(
  623. "google.auth._helpers.utcnow",
  624. return_value=datetime.datetime.min + _helpers.REFRESH_THRESHOLD,
  625. )
  626. def test_credentials_with_scopes_refresh_different_granted_scopes(
  627. self, unused_utcnow, refresh_grant
  628. ):
  629. scopes = ["email", "profile"]
  630. scopes_returned = ["email"]
  631. token = "token"
  632. new_rapt_token = "new_rapt_token"
  633. expiry = _helpers.utcnow() + datetime.timedelta(seconds=500)
  634. grant_response = {
  635. "id_token": mock.sentinel.id_token,
  636. "scope": " ".join(scopes_returned),
  637. }
  638. refresh_grant.return_value = (
  639. # Access token
  640. token,
  641. # New refresh token
  642. None,
  643. # Expiry,
  644. expiry,
  645. # Extra data
  646. grant_response,
  647. # rapt token
  648. new_rapt_token,
  649. )
  650. request = mock.create_autospec(transport.Request)
  651. creds = credentials.Credentials(
  652. token=None,
  653. refresh_token=self.REFRESH_TOKEN,
  654. token_uri=self.TOKEN_URI,
  655. client_id=self.CLIENT_ID,
  656. client_secret=self.CLIENT_SECRET,
  657. scopes=scopes,
  658. rapt_token=self.RAPT_TOKEN,
  659. enable_reauth_refresh=True,
  660. )
  661. # Refresh credentials
  662. creds.refresh(request)
  663. # Check jwt grant call.
  664. refresh_grant.assert_called_with(
  665. request,
  666. self.TOKEN_URI,
  667. self.REFRESH_TOKEN,
  668. self.CLIENT_ID,
  669. self.CLIENT_SECRET,
  670. scopes,
  671. self.RAPT_TOKEN,
  672. True,
  673. )
  674. # Check that the credentials have the token and expiry
  675. assert creds.token == token
  676. assert creds.expiry == expiry
  677. assert creds.id_token == mock.sentinel.id_token
  678. assert creds.has_scopes(scopes)
  679. assert creds.rapt_token == new_rapt_token
  680. assert creds.granted_scopes == scopes_returned
  681. # Check that the credentials are valid (have a token and are not
  682. # expired.)
  683. assert creds.valid
  684. def test_apply_with_quota_project_id(self):
  685. creds = credentials.Credentials(
  686. token="token",
  687. refresh_token=self.REFRESH_TOKEN,
  688. token_uri=self.TOKEN_URI,
  689. client_id=self.CLIENT_ID,
  690. client_secret=self.CLIENT_SECRET,
  691. quota_project_id="quota-project-123",
  692. )
  693. headers = {}
  694. creds.apply(headers)
  695. assert headers["x-goog-user-project"] == "quota-project-123"
  696. assert "token" in headers["authorization"]
  697. def test_apply_with_no_quota_project_id(self):
  698. creds = credentials.Credentials(
  699. token="token",
  700. refresh_token=self.REFRESH_TOKEN,
  701. token_uri=self.TOKEN_URI,
  702. client_id=self.CLIENT_ID,
  703. client_secret=self.CLIENT_SECRET,
  704. )
  705. headers = {}
  706. creds.apply(headers)
  707. assert "x-goog-user-project" not in headers
  708. assert "token" in headers["authorization"]
  709. def test_with_quota_project(self):
  710. creds = credentials.Credentials(
  711. token="token",
  712. refresh_token=self.REFRESH_TOKEN,
  713. token_uri=self.TOKEN_URI,
  714. client_id=self.CLIENT_ID,
  715. client_secret=self.CLIENT_SECRET,
  716. quota_project_id="quota-project-123",
  717. )
  718. new_creds = creds.with_quota_project("new-project-456")
  719. assert new_creds.quota_project_id == "new-project-456"
  720. headers = {}
  721. creds.apply(headers)
  722. assert "x-goog-user-project" in headers
  723. def test_with_universe_domain(self):
  724. creds = credentials.Credentials(token="token")
  725. assert creds.universe_domain == "googleapis.com"
  726. new_creds = creds.with_universe_domain("dummy_universe.com")
  727. assert new_creds.universe_domain == "dummy_universe.com"
  728. def test_with_account(self):
  729. creds = credentials.Credentials(token="token")
  730. assert creds.account == ""
  731. new_creds = creds.with_account("mock@example.com")
  732. assert new_creds.account == "mock@example.com"
  733. def test_with_token_uri(self):
  734. info = AUTH_USER_INFO.copy()
  735. creds = credentials.Credentials.from_authorized_user_info(info)
  736. new_token_uri = "https://oauth2-eu.googleapis.com/token"
  737. assert creds._token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  738. creds_with_new_token_uri = creds.with_token_uri(new_token_uri)
  739. assert creds_with_new_token_uri._token_uri == new_token_uri
  740. def test_from_authorized_user_info(self):
  741. info = AUTH_USER_INFO.copy()
  742. creds = credentials.Credentials.from_authorized_user_info(info)
  743. assert creds.client_secret == info["client_secret"]
  744. assert creds.client_id == info["client_id"]
  745. assert creds.refresh_token == info["refresh_token"]
  746. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  747. assert creds.scopes is None
  748. scopes = ["email", "profile"]
  749. creds = credentials.Credentials.from_authorized_user_info(info, scopes)
  750. assert creds.client_secret == info["client_secret"]
  751. assert creds.client_id == info["client_id"]
  752. assert creds.refresh_token == info["refresh_token"]
  753. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  754. assert creds.scopes == scopes
  755. info["scopes"] = "email" # single non-array scope from file
  756. creds = credentials.Credentials.from_authorized_user_info(info)
  757. assert creds.scopes == [info["scopes"]]
  758. info["scopes"] = ["email", "profile"] # array scope from file
  759. creds = credentials.Credentials.from_authorized_user_info(info)
  760. assert creds.scopes == info["scopes"]
  761. expiry = datetime.datetime(2020, 8, 14, 15, 54, 1)
  762. info["expiry"] = expiry.isoformat() + "Z"
  763. creds = credentials.Credentials.from_authorized_user_info(info)
  764. assert creds.expiry == expiry
  765. assert creds.expired
  766. def test_from_authorized_user_file(self):
  767. info = AUTH_USER_INFO.copy()
  768. creds = credentials.Credentials.from_authorized_user_file(AUTH_USER_JSON_FILE)
  769. assert creds.client_secret == info["client_secret"]
  770. assert creds.client_id == info["client_id"]
  771. assert creds.refresh_token == info["refresh_token"]
  772. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  773. assert creds.scopes is None
  774. assert creds.rapt_token is None
  775. scopes = ["email", "profile"]
  776. creds = credentials.Credentials.from_authorized_user_file(
  777. AUTH_USER_JSON_FILE, scopes
  778. )
  779. assert creds.client_secret == info["client_secret"]
  780. assert creds.client_id == info["client_id"]
  781. assert creds.refresh_token == info["refresh_token"]
  782. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  783. assert creds.scopes == scopes
  784. def test_from_authorized_user_file_with_rapt_token(self):
  785. info = AUTH_USER_INFO.copy()
  786. file_path = os.path.join(DATA_DIR, "authorized_user_with_rapt_token.json")
  787. creds = credentials.Credentials.from_authorized_user_file(file_path)
  788. assert creds.client_secret == info["client_secret"]
  789. assert creds.client_id == info["client_id"]
  790. assert creds.refresh_token == info["refresh_token"]
  791. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  792. assert creds.scopes is None
  793. assert creds.rapt_token == "rapt"
  794. def test_to_json(self):
  795. info = AUTH_USER_INFO.copy()
  796. expiry = datetime.datetime(2020, 8, 14, 15, 54, 1)
  797. info["expiry"] = expiry.isoformat() + "Z"
  798. creds = credentials.Credentials.from_authorized_user_info(info)
  799. assert creds.expiry == expiry
  800. # Test with no `strip` arg
  801. json_output = creds.to_json()
  802. json_asdict = json.loads(json_output)
  803. assert json_asdict.get("token") == creds.token
  804. assert json_asdict.get("refresh_token") == creds.refresh_token
  805. assert json_asdict.get("token_uri") == creds.token_uri
  806. assert json_asdict.get("client_id") == creds.client_id
  807. assert json_asdict.get("scopes") == creds.scopes
  808. assert json_asdict.get("client_secret") == creds.client_secret
  809. assert json_asdict.get("expiry") == info["expiry"]
  810. assert json_asdict.get("universe_domain") == creds.universe_domain
  811. assert json_asdict.get("account") == creds.account
  812. # Test with a `strip` arg
  813. json_output = creds.to_json(strip=["client_secret"])
  814. json_asdict = json.loads(json_output)
  815. assert json_asdict.get("token") == creds.token
  816. assert json_asdict.get("refresh_token") == creds.refresh_token
  817. assert json_asdict.get("token_uri") == creds.token_uri
  818. assert json_asdict.get("client_id") == creds.client_id
  819. assert json_asdict.get("scopes") == creds.scopes
  820. assert json_asdict.get("client_secret") is None
  821. # Test with no expiry
  822. creds.expiry = None
  823. json_output = creds.to_json()
  824. json_asdict = json.loads(json_output)
  825. assert json_asdict.get("expiry") is None
  826. def test_pickle_and_unpickle(self):
  827. creds = self.make_credentials()
  828. unpickled = pickle.loads(pickle.dumps(creds))
  829. # make sure attributes aren't lost during pickling
  830. assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort()
  831. for attr in list(creds.__dict__):
  832. # Worker should always be None
  833. if attr == "_refresh_worker":
  834. assert getattr(unpickled, attr) is None
  835. else:
  836. assert getattr(creds, attr) == getattr(unpickled, attr)
  837. def test_pickle_and_unpickle_universe_domain(self):
  838. # old version of auth lib doesn't have _universe_domain, so the pickled
  839. # cred doesn't have such a field.
  840. creds = self.make_credentials()
  841. del creds._universe_domain
  842. unpickled = pickle.loads(pickle.dumps(creds))
  843. # make sure the unpickled cred sets _universe_domain to default.
  844. assert unpickled.universe_domain == "googleapis.com"
  845. def test_pickle_and_unpickle_with_refresh_handler(self):
  846. expected_expiry = _helpers.utcnow() + datetime.timedelta(seconds=2800)
  847. refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry))
  848. creds = credentials.Credentials(
  849. token=None,
  850. refresh_token=None,
  851. token_uri=None,
  852. client_id=None,
  853. client_secret=None,
  854. rapt_token=None,
  855. refresh_handler=refresh_handler,
  856. )
  857. unpickled = pickle.loads(pickle.dumps(creds))
  858. # make sure attributes aren't lost during pickling
  859. assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort()
  860. for attr in list(creds.__dict__):
  861. # For the _refresh_handler property, the unpickled creds should be
  862. # set to None.
  863. if attr == "_refresh_handler" or attr == "_refresh_worker":
  864. assert getattr(unpickled, attr) is None
  865. else:
  866. assert getattr(creds, attr) == getattr(unpickled, attr)
  867. def test_pickle_with_missing_attribute(self):
  868. creds = self.make_credentials()
  869. # remove an optional attribute before pickling
  870. # this mimics a pickle created with a previous class definition with
  871. # fewer attributes
  872. del creds.__dict__["_quota_project_id"]
  873. del creds.__dict__["_refresh_handler"]
  874. del creds.__dict__["_refresh_worker"]
  875. unpickled = pickle.loads(pickle.dumps(creds))
  876. # Attribute should be initialized by `__setstate__`
  877. assert unpickled.quota_project_id is None
  878. # pickles are not compatible across versions
  879. @pytest.mark.skipif(
  880. sys.version_info < (3, 5),
  881. reason="pickle file can only be loaded with Python >= 3.5",
  882. )
  883. def test_unpickle_old_credentials_pickle(self):
  884. # make sure a credentials file pickled with an older
  885. # library version (google-auth==1.5.1) can be unpickled
  886. with open(
  887. os.path.join(DATA_DIR, "old_oauth_credentials_py3.pickle"), "rb"
  888. ) as f:
  889. credentials = pickle.load(f)
  890. assert credentials.quota_project_id is None
  891. class TestUserAccessTokenCredentials(object):
  892. def test_instance(self):
  893. with pytest.warns(
  894. UserWarning, match="UserAccessTokenCredentials is deprecated"
  895. ):
  896. cred = credentials.UserAccessTokenCredentials()
  897. assert cred._account is None
  898. cred = cred.with_account("account")
  899. assert cred._account == "account"
  900. @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True)
  901. def test_refresh(self, get_auth_access_token):
  902. with pytest.warns(
  903. UserWarning, match="UserAccessTokenCredentials is deprecated"
  904. ):
  905. get_auth_access_token.return_value = "access_token"
  906. cred = credentials.UserAccessTokenCredentials()
  907. cred.refresh(None)
  908. assert cred.token == "access_token"
  909. def test_with_quota_project(self):
  910. with pytest.warns(
  911. UserWarning, match="UserAccessTokenCredentials is deprecated"
  912. ):
  913. cred = credentials.UserAccessTokenCredentials()
  914. quota_project_cred = cred.with_quota_project("project-foo")
  915. assert quota_project_cred._quota_project_id == "project-foo"
  916. assert quota_project_cred._account == cred._account
  917. @mock.patch(
  918. "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True
  919. )
  920. @mock.patch(
  921. "google.oauth2.credentials.UserAccessTokenCredentials.refresh", autospec=True
  922. )
  923. def test_before_request(self, refresh, apply):
  924. with pytest.warns(
  925. UserWarning, match="UserAccessTokenCredentials is deprecated"
  926. ):
  927. cred = credentials.UserAccessTokenCredentials()
  928. cred.before_request(mock.Mock(), "GET", "https://example.com", {})
  929. refresh.assert_called()
  930. apply.assert_called()