test_credentials.py 37 KB

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