test_credentials.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  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_account(self):
  706. creds = credentials.Credentials(token="token")
  707. assert creds.account == ""
  708. new_creds = creds.with_account("mock@example.com")
  709. assert new_creds.account == "mock@example.com"
  710. def test_with_token_uri(self):
  711. info = AUTH_USER_INFO.copy()
  712. creds = credentials.Credentials.from_authorized_user_info(info)
  713. new_token_uri = "https://oauth2-eu.googleapis.com/token"
  714. assert creds._token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  715. creds_with_new_token_uri = creds.with_token_uri(new_token_uri)
  716. assert creds_with_new_token_uri._token_uri == new_token_uri
  717. def test_from_authorized_user_info(self):
  718. info = AUTH_USER_INFO.copy()
  719. creds = credentials.Credentials.from_authorized_user_info(info)
  720. assert creds.client_secret == info["client_secret"]
  721. assert creds.client_id == info["client_id"]
  722. assert creds.refresh_token == info["refresh_token"]
  723. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  724. assert creds.scopes is None
  725. scopes = ["email", "profile"]
  726. creds = credentials.Credentials.from_authorized_user_info(info, scopes)
  727. assert creds.client_secret == info["client_secret"]
  728. assert creds.client_id == info["client_id"]
  729. assert creds.refresh_token == info["refresh_token"]
  730. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  731. assert creds.scopes == scopes
  732. info["scopes"] = "email" # single non-array scope from file
  733. creds = credentials.Credentials.from_authorized_user_info(info)
  734. assert creds.scopes == [info["scopes"]]
  735. info["scopes"] = ["email", "profile"] # array scope from file
  736. creds = credentials.Credentials.from_authorized_user_info(info)
  737. assert creds.scopes == info["scopes"]
  738. expiry = datetime.datetime(2020, 8, 14, 15, 54, 1)
  739. info["expiry"] = expiry.isoformat() + "Z"
  740. creds = credentials.Credentials.from_authorized_user_info(info)
  741. assert creds.expiry == expiry
  742. assert creds.expired
  743. def test_from_authorized_user_file(self):
  744. info = AUTH_USER_INFO.copy()
  745. creds = credentials.Credentials.from_authorized_user_file(AUTH_USER_JSON_FILE)
  746. assert creds.client_secret == info["client_secret"]
  747. assert creds.client_id == info["client_id"]
  748. assert creds.refresh_token == info["refresh_token"]
  749. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  750. assert creds.scopes is None
  751. assert creds.rapt_token is None
  752. scopes = ["email", "profile"]
  753. creds = credentials.Credentials.from_authorized_user_file(
  754. AUTH_USER_JSON_FILE, scopes
  755. )
  756. assert creds.client_secret == info["client_secret"]
  757. assert creds.client_id == info["client_id"]
  758. assert creds.refresh_token == info["refresh_token"]
  759. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  760. assert creds.scopes == scopes
  761. def test_from_authorized_user_file_with_rapt_token(self):
  762. info = AUTH_USER_INFO.copy()
  763. file_path = os.path.join(DATA_DIR, "authorized_user_with_rapt_token.json")
  764. creds = credentials.Credentials.from_authorized_user_file(file_path)
  765. assert creds.client_secret == info["client_secret"]
  766. assert creds.client_id == info["client_id"]
  767. assert creds.refresh_token == info["refresh_token"]
  768. assert creds.token_uri == credentials._GOOGLE_OAUTH2_TOKEN_ENDPOINT
  769. assert creds.scopes is None
  770. assert creds.rapt_token == "rapt"
  771. def test_to_json(self):
  772. info = AUTH_USER_INFO.copy()
  773. expiry = datetime.datetime(2020, 8, 14, 15, 54, 1)
  774. info["expiry"] = expiry.isoformat() + "Z"
  775. creds = credentials.Credentials.from_authorized_user_info(info)
  776. assert creds.expiry == expiry
  777. # Test with no `strip` arg
  778. json_output = creds.to_json()
  779. json_asdict = json.loads(json_output)
  780. assert json_asdict.get("token") == creds.token
  781. assert json_asdict.get("refresh_token") == creds.refresh_token
  782. assert json_asdict.get("token_uri") == creds.token_uri
  783. assert json_asdict.get("client_id") == creds.client_id
  784. assert json_asdict.get("scopes") == creds.scopes
  785. assert json_asdict.get("client_secret") == creds.client_secret
  786. assert json_asdict.get("expiry") == info["expiry"]
  787. assert json_asdict.get("universe_domain") == creds.universe_domain
  788. assert json_asdict.get("account") == creds.account
  789. # Test with a `strip` arg
  790. json_output = creds.to_json(strip=["client_secret"])
  791. json_asdict = json.loads(json_output)
  792. assert json_asdict.get("token") == creds.token
  793. assert json_asdict.get("refresh_token") == creds.refresh_token
  794. assert json_asdict.get("token_uri") == creds.token_uri
  795. assert json_asdict.get("client_id") == creds.client_id
  796. assert json_asdict.get("scopes") == creds.scopes
  797. assert json_asdict.get("client_secret") is None
  798. # Test with no expiry
  799. creds.expiry = None
  800. json_output = creds.to_json()
  801. json_asdict = json.loads(json_output)
  802. assert json_asdict.get("expiry") is None
  803. def test_pickle_and_unpickle(self):
  804. creds = self.make_credentials()
  805. unpickled = pickle.loads(pickle.dumps(creds))
  806. # make sure attributes aren't lost during pickling
  807. assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort()
  808. for attr in list(creds.__dict__):
  809. # Worker should always be None
  810. if attr == "_refresh_worker":
  811. assert getattr(unpickled, attr) is None
  812. else:
  813. assert getattr(creds, attr) == getattr(unpickled, attr)
  814. def test_pickle_and_unpickle_universe_domain(self):
  815. # old version of auth lib doesn't have _universe_domain, so the pickled
  816. # cred doesn't have such a field.
  817. creds = self.make_credentials()
  818. del creds._universe_domain
  819. unpickled = pickle.loads(pickle.dumps(creds))
  820. # make sure the unpickled cred sets _universe_domain to default.
  821. assert unpickled.universe_domain == "googleapis.com"
  822. def test_pickle_and_unpickle_with_refresh_handler(self):
  823. expected_expiry = _helpers.utcnow() + datetime.timedelta(seconds=2800)
  824. refresh_handler = mock.Mock(return_value=("TOKEN", expected_expiry))
  825. creds = credentials.Credentials(
  826. token=None,
  827. refresh_token=None,
  828. token_uri=None,
  829. client_id=None,
  830. client_secret=None,
  831. rapt_token=None,
  832. refresh_handler=refresh_handler,
  833. )
  834. unpickled = pickle.loads(pickle.dumps(creds))
  835. # make sure attributes aren't lost during pickling
  836. assert list(creds.__dict__).sort() == list(unpickled.__dict__).sort()
  837. for attr in list(creds.__dict__):
  838. # For the _refresh_handler property, the unpickled creds should be
  839. # set to None.
  840. if attr == "_refresh_handler" or attr == "_refresh_worker":
  841. assert getattr(unpickled, attr) is None
  842. else:
  843. assert getattr(creds, attr) == getattr(unpickled, attr)
  844. def test_pickle_with_missing_attribute(self):
  845. creds = self.make_credentials()
  846. # remove an optional attribute before pickling
  847. # this mimics a pickle created with a previous class definition with
  848. # fewer attributes
  849. del creds.__dict__["_quota_project_id"]
  850. del creds.__dict__["_refresh_handler"]
  851. del creds.__dict__["_refresh_worker"]
  852. unpickled = pickle.loads(pickle.dumps(creds))
  853. # Attribute should be initialized by `__setstate__`
  854. assert unpickled.quota_project_id is None
  855. # pickles are not compatible across versions
  856. @pytest.mark.skipif(
  857. sys.version_info < (3, 5),
  858. reason="pickle file can only be loaded with Python >= 3.5",
  859. )
  860. def test_unpickle_old_credentials_pickle(self):
  861. # make sure a credentials file pickled with an older
  862. # library version (google-auth==1.5.1) can be unpickled
  863. with open(
  864. os.path.join(DATA_DIR, "old_oauth_credentials_py3.pickle"), "rb"
  865. ) as f:
  866. credentials = pickle.load(f)
  867. assert credentials.quota_project_id is None
  868. class TestUserAccessTokenCredentials(object):
  869. def test_instance(self):
  870. with pytest.warns(
  871. UserWarning, match="UserAccessTokenCredentials is deprecated"
  872. ):
  873. cred = credentials.UserAccessTokenCredentials()
  874. assert cred._account is None
  875. cred = cred.with_account("account")
  876. assert cred._account == "account"
  877. @mock.patch("google.auth._cloud_sdk.get_auth_access_token", autospec=True)
  878. def test_refresh(self, get_auth_access_token):
  879. with pytest.warns(
  880. UserWarning, match="UserAccessTokenCredentials is deprecated"
  881. ):
  882. get_auth_access_token.return_value = "access_token"
  883. cred = credentials.UserAccessTokenCredentials()
  884. cred.refresh(None)
  885. assert cred.token == "access_token"
  886. def test_with_quota_project(self):
  887. with pytest.warns(
  888. UserWarning, match="UserAccessTokenCredentials is deprecated"
  889. ):
  890. cred = credentials.UserAccessTokenCredentials()
  891. quota_project_cred = cred.with_quota_project("project-foo")
  892. assert quota_project_cred._quota_project_id == "project-foo"
  893. assert quota_project_cred._account == cred._account
  894. @mock.patch(
  895. "google.oauth2.credentials.UserAccessTokenCredentials.apply", autospec=True
  896. )
  897. @mock.patch(
  898. "google.oauth2.credentials.UserAccessTokenCredentials.refresh", autospec=True
  899. )
  900. def test_before_request(self, refresh, apply):
  901. with pytest.warns(
  902. UserWarning, match="UserAccessTokenCredentials is deprecated"
  903. ):
  904. cred = credentials.UserAccessTokenCredentials()
  905. cred.before_request(mock.Mock(), "GET", "https://example.com", {})
  906. refresh.assert_called()
  907. apply.assert_called()