test_credentials.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  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 base64
  15. import datetime
  16. import mock
  17. import pytest
  18. import responses
  19. from google.auth import _helpers
  20. from google.auth import exceptions
  21. from google.auth import jwt
  22. from google.auth import transport
  23. from google.auth.compute_engine import credentials
  24. from google.auth.transport import requests
  25. SAMPLE_ID_TOKEN_EXP = 1584393400
  26. # header: {"alg": "RS256", "typ": "JWT", "kid": "1"}
  27. # payload: {"iss": "issuer", "iat": 1584393348, "sub": "subject",
  28. # "exp": 1584393400,"aud": "audience"}
  29. SAMPLE_ID_TOKEN = (
  30. b"eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCIsICJraWQiOiAiMSJ9."
  31. b"eyJpc3MiOiAiaXNzdWVyIiwgImlhdCI6IDE1ODQzOTMzNDgsICJzdWIiO"
  32. b"iAic3ViamVjdCIsICJleHAiOiAxNTg0MzkzNDAwLCAiYXVkIjogImF1ZG"
  33. b"llbmNlIn0."
  34. b"OquNjHKhTmlgCk361omRo18F_uY-7y0f_AmLbzW062Q1Zr61HAwHYP5FM"
  35. b"316CK4_0cH8MUNGASsvZc3VqXAqub6PUTfhemH8pFEwBdAdG0LhrNkU0H"
  36. b"WN1YpT55IiQ31esLdL5q-qDsOPpNZJUti1y1lAreM5nIn2srdWzGXGs4i"
  37. b"TRQsn0XkNUCL4RErpciXmjfhMrPkcAjKA-mXQm2fa4jmTlEZFqFmUlym1"
  38. b"ozJ0yf5grjN6AslN4OGvAv1pS-_Ko_pGBS6IQtSBC6vVKCUuBfaqNjykg"
  39. b"bsxbLa6Fp0SYeYwO8ifEnkRvasVpc1WTQqfRB2JCj5pTBDzJpIpFCMmnQ"
  40. )
  41. class TestCredentials(object):
  42. credentials = None
  43. @pytest.fixture(autouse=True)
  44. def credentials_fixture(self):
  45. self.credentials = credentials.Credentials()
  46. def test_default_state(self):
  47. assert not self.credentials.valid
  48. # Expiration hasn't been set yet
  49. assert not self.credentials.expired
  50. # Scopes are needed
  51. assert self.credentials.requires_scopes
  52. # Service account email hasn't been populated
  53. assert self.credentials.service_account_email == "default"
  54. # No quota project
  55. assert not self.credentials._quota_project_id
  56. @mock.patch(
  57. "google.auth._helpers.utcnow",
  58. return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
  59. )
  60. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  61. def test_refresh_success(self, get, utcnow):
  62. get.side_effect = [
  63. {
  64. # First request is for sevice account info.
  65. "email": "service-account@example.com",
  66. "scopes": ["one", "two"],
  67. },
  68. {
  69. # Second request is for the token.
  70. "access_token": "token",
  71. "expires_in": 500,
  72. },
  73. ]
  74. # Refresh credentials
  75. self.credentials.refresh(None)
  76. # Check that the credentials have the token and proper expiration
  77. assert self.credentials.token == "token"
  78. assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500))
  79. # Check the credential info
  80. assert self.credentials.service_account_email == "service-account@example.com"
  81. assert self.credentials._scopes == ["one", "two"]
  82. # Check that the credentials are valid (have a token and are not
  83. # expired)
  84. assert self.credentials.valid
  85. @mock.patch(
  86. "google.auth._helpers.utcnow",
  87. return_value=datetime.datetime.min + _helpers.CLOCK_SKEW,
  88. )
  89. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  90. def test_refresh_success_with_scopes(self, get, utcnow):
  91. get.side_effect = [
  92. {
  93. # First request is for sevice account info.
  94. "email": "service-account@example.com",
  95. "scopes": ["one", "two"],
  96. },
  97. {
  98. # Second request is for the token.
  99. "access_token": "token",
  100. "expires_in": 500,
  101. },
  102. ]
  103. # Refresh credentials
  104. scopes = ["three", "four"]
  105. self.credentials = self.credentials.with_scopes(scopes)
  106. self.credentials.refresh(None)
  107. # Check that the credentials have the token and proper expiration
  108. assert self.credentials.token == "token"
  109. assert self.credentials.expiry == (utcnow() + datetime.timedelta(seconds=500))
  110. # Check the credential info
  111. assert self.credentials.service_account_email == "service-account@example.com"
  112. assert self.credentials._scopes == scopes
  113. # Check that the credentials are valid (have a token and are not
  114. # expired)
  115. assert self.credentials.valid
  116. kwargs = get.call_args[1]
  117. assert kwargs == {"params": {"scopes": "three,four"}}
  118. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  119. def test_refresh_error(self, get):
  120. get.side_effect = exceptions.TransportError("http error")
  121. with pytest.raises(exceptions.RefreshError) as excinfo:
  122. self.credentials.refresh(None)
  123. assert excinfo.match(r"http error")
  124. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  125. def test_before_request_refreshes(self, get):
  126. get.side_effect = [
  127. {
  128. # First request is for sevice account info.
  129. "email": "service-account@example.com",
  130. "scopes": "one two",
  131. },
  132. {
  133. # Second request is for the token.
  134. "access_token": "token",
  135. "expires_in": 500,
  136. },
  137. ]
  138. # Credentials should start as invalid
  139. assert not self.credentials.valid
  140. # before_request should cause a refresh
  141. request = mock.create_autospec(transport.Request, instance=True)
  142. self.credentials.before_request(request, "GET", "http://example.com?a=1#3", {})
  143. # The refresh endpoint should've been called.
  144. assert get.called
  145. # Credentials should now be valid.
  146. assert self.credentials.valid
  147. def test_with_quota_project(self):
  148. quota_project_creds = self.credentials.with_quota_project("project-foo")
  149. assert quota_project_creds._quota_project_id == "project-foo"
  150. def test_with_scopes(self):
  151. assert self.credentials._scopes is None
  152. scopes = ["one", "two"]
  153. self.credentials = self.credentials.with_scopes(scopes)
  154. assert self.credentials._scopes == scopes
  155. class TestIDTokenCredentials(object):
  156. credentials = None
  157. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  158. def test_default_state(self, get):
  159. get.side_effect = [
  160. {"email": "service-account@example.com", "scope": ["one", "two"]}
  161. ]
  162. request = mock.create_autospec(transport.Request, instance=True)
  163. self.credentials = credentials.IDTokenCredentials(
  164. request=request, target_audience="https://example.com"
  165. )
  166. assert not self.credentials.valid
  167. # Expiration hasn't been set yet
  168. assert not self.credentials.expired
  169. # Service account email hasn't been populated
  170. assert self.credentials.service_account_email == "service-account@example.com"
  171. # Signer is initialized
  172. assert self.credentials.signer
  173. assert self.credentials.signer_email == "service-account@example.com"
  174. # No quota project
  175. assert not self.credentials._quota_project_id
  176. @mock.patch(
  177. "google.auth._helpers.utcnow",
  178. return_value=datetime.datetime.utcfromtimestamp(0),
  179. )
  180. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  181. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  182. def test_make_authorization_grant_assertion(self, sign, get, utcnow):
  183. get.side_effect = [
  184. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  185. ]
  186. sign.side_effect = [b"signature"]
  187. request = mock.create_autospec(transport.Request, instance=True)
  188. self.credentials = credentials.IDTokenCredentials(
  189. request=request, target_audience="https://audience.com"
  190. )
  191. # Generate authorization grant:
  192. token = self.credentials._make_authorization_grant_assertion()
  193. payload = jwt.decode(token, verify=False)
  194. # The JWT token signature is 'signature' encoded in base 64:
  195. assert token.endswith(b".c2lnbmF0dXJl")
  196. # Check that the credentials have the token and proper expiration
  197. assert payload == {
  198. "aud": "https://www.googleapis.com/oauth2/v4/token",
  199. "exp": 3600,
  200. "iat": 0,
  201. "iss": "service-account@example.com",
  202. "target_audience": "https://audience.com",
  203. }
  204. @mock.patch(
  205. "google.auth._helpers.utcnow",
  206. return_value=datetime.datetime.utcfromtimestamp(0),
  207. )
  208. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  209. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  210. def test_with_service_account(self, sign, get, utcnow):
  211. sign.side_effect = [b"signature"]
  212. request = mock.create_autospec(transport.Request, instance=True)
  213. self.credentials = credentials.IDTokenCredentials(
  214. request=request,
  215. target_audience="https://audience.com",
  216. service_account_email="service-account@other.com",
  217. )
  218. # Generate authorization grant:
  219. token = self.credentials._make_authorization_grant_assertion()
  220. payload = jwt.decode(token, verify=False)
  221. # The JWT token signature is 'signature' encoded in base 64:
  222. assert token.endswith(b".c2lnbmF0dXJl")
  223. # Check that the credentials have the token and proper expiration
  224. assert payload == {
  225. "aud": "https://www.googleapis.com/oauth2/v4/token",
  226. "exp": 3600,
  227. "iat": 0,
  228. "iss": "service-account@other.com",
  229. "target_audience": "https://audience.com",
  230. }
  231. @mock.patch(
  232. "google.auth._helpers.utcnow",
  233. return_value=datetime.datetime.utcfromtimestamp(0),
  234. )
  235. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  236. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  237. def test_additional_claims(self, sign, get, utcnow):
  238. get.side_effect = [
  239. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  240. ]
  241. sign.side_effect = [b"signature"]
  242. request = mock.create_autospec(transport.Request, instance=True)
  243. self.credentials = credentials.IDTokenCredentials(
  244. request=request,
  245. target_audience="https://audience.com",
  246. additional_claims={"foo": "bar"},
  247. )
  248. # Generate authorization grant:
  249. token = self.credentials._make_authorization_grant_assertion()
  250. payload = jwt.decode(token, verify=False)
  251. # The JWT token signature is 'signature' encoded in base 64:
  252. assert token.endswith(b".c2lnbmF0dXJl")
  253. # Check that the credentials have the token and proper expiration
  254. assert payload == {
  255. "aud": "https://www.googleapis.com/oauth2/v4/token",
  256. "exp": 3600,
  257. "iat": 0,
  258. "iss": "service-account@example.com",
  259. "target_audience": "https://audience.com",
  260. "foo": "bar",
  261. }
  262. def test_token_uri(self):
  263. request = mock.create_autospec(transport.Request, instance=True)
  264. self.credentials = credentials.IDTokenCredentials(
  265. request=request,
  266. signer=mock.Mock(),
  267. service_account_email="foo@example.com",
  268. target_audience="https://audience.com",
  269. )
  270. assert self.credentials._token_uri == credentials._DEFAULT_TOKEN_URI
  271. self.credentials = credentials.IDTokenCredentials(
  272. request=request,
  273. signer=mock.Mock(),
  274. service_account_email="foo@example.com",
  275. target_audience="https://audience.com",
  276. token_uri="https://example.com/token",
  277. )
  278. assert self.credentials._token_uri == "https://example.com/token"
  279. @mock.patch(
  280. "google.auth._helpers.utcnow",
  281. return_value=datetime.datetime.utcfromtimestamp(0),
  282. )
  283. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  284. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  285. def test_with_target_audience(self, sign, get, utcnow):
  286. get.side_effect = [
  287. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  288. ]
  289. sign.side_effect = [b"signature"]
  290. request = mock.create_autospec(transport.Request, instance=True)
  291. self.credentials = credentials.IDTokenCredentials(
  292. request=request, target_audience="https://audience.com"
  293. )
  294. self.credentials = self.credentials.with_target_audience("https://actually.not")
  295. # Generate authorization grant:
  296. token = self.credentials._make_authorization_grant_assertion()
  297. payload = jwt.decode(token, verify=False)
  298. # The JWT token signature is 'signature' encoded in base 64:
  299. assert token.endswith(b".c2lnbmF0dXJl")
  300. # Check that the credentials have the token and proper expiration
  301. assert payload == {
  302. "aud": "https://www.googleapis.com/oauth2/v4/token",
  303. "exp": 3600,
  304. "iat": 0,
  305. "iss": "service-account@example.com",
  306. "target_audience": "https://actually.not",
  307. }
  308. # Check that the signer have been initialized with a Request object
  309. assert isinstance(self.credentials._signer._request, transport.Request)
  310. @responses.activate
  311. def test_with_target_audience_integration(self):
  312. """ Test that it is possible to refresh credentials
  313. generated from `with_target_audience`.
  314. Instead of mocking the methods, the HTTP responses
  315. have been mocked.
  316. """
  317. # mock information about credentials
  318. responses.add(
  319. responses.GET,
  320. "http://metadata.google.internal/computeMetadata/v1/instance/"
  321. "service-accounts/default/?recursive=true",
  322. status=200,
  323. content_type="application/json",
  324. json={
  325. "scopes": "email",
  326. "email": "service-account@example.com",
  327. "aliases": ["default"],
  328. },
  329. )
  330. # mock token for credentials
  331. responses.add(
  332. responses.GET,
  333. "http://metadata.google.internal/computeMetadata/v1/instance/"
  334. "service-accounts/service-account@example.com/token",
  335. status=200,
  336. content_type="application/json",
  337. json={
  338. "access_token": "some-token",
  339. "expires_in": 3210,
  340. "token_type": "Bearer",
  341. },
  342. )
  343. # mock sign blob endpoint
  344. signature = base64.b64encode(b"some-signature").decode("utf-8")
  345. responses.add(
  346. responses.POST,
  347. "https://iamcredentials.googleapis.com/v1/projects/-/"
  348. "serviceAccounts/service-account@example.com:signBlob?alt=json",
  349. status=200,
  350. content_type="application/json",
  351. json={"keyId": "some-key-id", "signedBlob": signature},
  352. )
  353. id_token = "{}.{}.{}".format(
  354. base64.b64encode(b'{"some":"some"}').decode("utf-8"),
  355. base64.b64encode(b'{"exp": 3210}').decode("utf-8"),
  356. base64.b64encode(b"token").decode("utf-8"),
  357. )
  358. # mock id token endpoint
  359. responses.add(
  360. responses.POST,
  361. "https://www.googleapis.com/oauth2/v4/token",
  362. status=200,
  363. content_type="application/json",
  364. json={"id_token": id_token, "expiry": 3210},
  365. )
  366. self.credentials = credentials.IDTokenCredentials(
  367. request=requests.Request(),
  368. service_account_email="service-account@example.com",
  369. target_audience="https://audience.com",
  370. )
  371. self.credentials = self.credentials.with_target_audience("https://actually.not")
  372. self.credentials.refresh(requests.Request())
  373. assert self.credentials.token is not None
  374. @mock.patch(
  375. "google.auth._helpers.utcnow",
  376. return_value=datetime.datetime.utcfromtimestamp(0),
  377. )
  378. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  379. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  380. def test_with_quota_project(self, sign, get, utcnow):
  381. get.side_effect = [
  382. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  383. ]
  384. sign.side_effect = [b"signature"]
  385. request = mock.create_autospec(transport.Request, instance=True)
  386. self.credentials = credentials.IDTokenCredentials(
  387. request=request, target_audience="https://audience.com"
  388. )
  389. self.credentials = self.credentials.with_quota_project("project-foo")
  390. assert self.credentials._quota_project_id == "project-foo"
  391. # Generate authorization grant:
  392. token = self.credentials._make_authorization_grant_assertion()
  393. payload = jwt.decode(token, verify=False)
  394. # The JWT token signature is 'signature' encoded in base 64:
  395. assert token.endswith(b".c2lnbmF0dXJl")
  396. # Check that the credentials have the token and proper expiration
  397. assert payload == {
  398. "aud": "https://www.googleapis.com/oauth2/v4/token",
  399. "exp": 3600,
  400. "iat": 0,
  401. "iss": "service-account@example.com",
  402. "target_audience": "https://audience.com",
  403. }
  404. # Check that the signer have been initialized with a Request object
  405. assert isinstance(self.credentials._signer._request, transport.Request)
  406. @responses.activate
  407. def test_with_quota_project_integration(self):
  408. """ Test that it is possible to refresh credentials
  409. generated from `with_quota_project`.
  410. Instead of mocking the methods, the HTTP responses
  411. have been mocked.
  412. """
  413. # mock information about credentials
  414. responses.add(
  415. responses.GET,
  416. "http://metadata.google.internal/computeMetadata/v1/instance/"
  417. "service-accounts/default/?recursive=true",
  418. status=200,
  419. content_type="application/json",
  420. json={
  421. "scopes": "email",
  422. "email": "service-account@example.com",
  423. "aliases": ["default"],
  424. },
  425. )
  426. # mock token for credentials
  427. responses.add(
  428. responses.GET,
  429. "http://metadata.google.internal/computeMetadata/v1/instance/"
  430. "service-accounts/service-account@example.com/token",
  431. status=200,
  432. content_type="application/json",
  433. json={
  434. "access_token": "some-token",
  435. "expires_in": 3210,
  436. "token_type": "Bearer",
  437. },
  438. )
  439. # mock sign blob endpoint
  440. signature = base64.b64encode(b"some-signature").decode("utf-8")
  441. responses.add(
  442. responses.POST,
  443. "https://iamcredentials.googleapis.com/v1/projects/-/"
  444. "serviceAccounts/service-account@example.com:signBlob?alt=json",
  445. status=200,
  446. content_type="application/json",
  447. json={"keyId": "some-key-id", "signedBlob": signature},
  448. )
  449. id_token = "{}.{}.{}".format(
  450. base64.b64encode(b'{"some":"some"}').decode("utf-8"),
  451. base64.b64encode(b'{"exp": 3210}').decode("utf-8"),
  452. base64.b64encode(b"token").decode("utf-8"),
  453. )
  454. # mock id token endpoint
  455. responses.add(
  456. responses.POST,
  457. "https://www.googleapis.com/oauth2/v4/token",
  458. status=200,
  459. content_type="application/json",
  460. json={"id_token": id_token, "expiry": 3210},
  461. )
  462. self.credentials = credentials.IDTokenCredentials(
  463. request=requests.Request(),
  464. service_account_email="service-account@example.com",
  465. target_audience="https://audience.com",
  466. )
  467. self.credentials = self.credentials.with_quota_project("project-foo")
  468. self.credentials.refresh(requests.Request())
  469. assert self.credentials.token is not None
  470. assert self.credentials._quota_project_id == "project-foo"
  471. @mock.patch(
  472. "google.auth._helpers.utcnow",
  473. return_value=datetime.datetime.utcfromtimestamp(0),
  474. )
  475. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  476. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  477. @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True)
  478. def test_refresh_success(self, id_token_jwt_grant, sign, get, utcnow):
  479. get.side_effect = [
  480. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  481. ]
  482. sign.side_effect = [b"signature"]
  483. id_token_jwt_grant.side_effect = [
  484. ("idtoken", datetime.datetime.utcfromtimestamp(3600), {})
  485. ]
  486. request = mock.create_autospec(transport.Request, instance=True)
  487. self.credentials = credentials.IDTokenCredentials(
  488. request=request, target_audience="https://audience.com"
  489. )
  490. # Refresh credentials
  491. self.credentials.refresh(None)
  492. # Check that the credentials have the token and proper expiration
  493. assert self.credentials.token == "idtoken"
  494. assert self.credentials.expiry == (datetime.datetime.utcfromtimestamp(3600))
  495. # Check the credential info
  496. assert self.credentials.service_account_email == "service-account@example.com"
  497. # Check that the credentials are valid (have a token and are not
  498. # expired)
  499. assert self.credentials.valid
  500. @mock.patch(
  501. "google.auth._helpers.utcnow",
  502. return_value=datetime.datetime.utcfromtimestamp(0),
  503. )
  504. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  505. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  506. def test_refresh_error(self, sign, get, utcnow):
  507. get.side_effect = [
  508. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  509. ]
  510. sign.side_effect = [b"signature"]
  511. request = mock.create_autospec(transport.Request, instance=True)
  512. response = mock.Mock()
  513. response.data = b'{"error": "http error"}'
  514. response.status = 500
  515. request.side_effect = [response]
  516. self.credentials = credentials.IDTokenCredentials(
  517. request=request, target_audience="https://audience.com"
  518. )
  519. with pytest.raises(exceptions.RefreshError) as excinfo:
  520. self.credentials.refresh(request)
  521. assert excinfo.match(r"http error")
  522. @mock.patch(
  523. "google.auth._helpers.utcnow",
  524. return_value=datetime.datetime.utcfromtimestamp(0),
  525. )
  526. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  527. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  528. @mock.patch("google.oauth2._client.id_token_jwt_grant", autospec=True)
  529. def test_before_request_refreshes(self, id_token_jwt_grant, sign, get, utcnow):
  530. get.side_effect = [
  531. {"email": "service-account@example.com", "scopes": "one two"}
  532. ]
  533. sign.side_effect = [b"signature"]
  534. id_token_jwt_grant.side_effect = [
  535. ("idtoken", datetime.datetime.utcfromtimestamp(3600), {})
  536. ]
  537. request = mock.create_autospec(transport.Request, instance=True)
  538. self.credentials = credentials.IDTokenCredentials(
  539. request=request, target_audience="https://audience.com"
  540. )
  541. # Credentials should start as invalid
  542. assert not self.credentials.valid
  543. # before_request should cause a refresh
  544. request = mock.create_autospec(transport.Request, instance=True)
  545. self.credentials.before_request(request, "GET", "http://example.com?a=1#3", {})
  546. # The refresh endpoint should've been called.
  547. assert get.called
  548. # Credentials should now be valid.
  549. assert self.credentials.valid
  550. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  551. @mock.patch("google.auth.iam.Signer.sign", autospec=True)
  552. def test_sign_bytes(self, sign, get):
  553. get.side_effect = [
  554. {"email": "service-account@example.com", "scopes": ["one", "two"]}
  555. ]
  556. sign.side_effect = [b"signature"]
  557. request = mock.create_autospec(transport.Request, instance=True)
  558. response = mock.Mock()
  559. response.data = b'{"signature": "c2lnbmF0dXJl"}'
  560. response.status = 200
  561. request.side_effect = [response]
  562. self.credentials = credentials.IDTokenCredentials(
  563. request=request, target_audience="https://audience.com"
  564. )
  565. # Generate authorization grant:
  566. signature = self.credentials.sign_bytes(b"some bytes")
  567. # The JWT token signature is 'signature' encoded in base 64:
  568. assert signature == b"signature"
  569. @mock.patch(
  570. "google.auth.compute_engine._metadata.get_service_account_info", autospec=True
  571. )
  572. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  573. def test_get_id_token_from_metadata(self, get, get_service_account_info):
  574. get.return_value = SAMPLE_ID_TOKEN
  575. get_service_account_info.return_value = {"email": "foo@example.com"}
  576. cred = credentials.IDTokenCredentials(
  577. mock.Mock(), "audience", use_metadata_identity_endpoint=True
  578. )
  579. cred.refresh(request=mock.Mock())
  580. assert cred.token == SAMPLE_ID_TOKEN
  581. assert cred.expiry == datetime.datetime.fromtimestamp(SAMPLE_ID_TOKEN_EXP)
  582. assert cred._use_metadata_identity_endpoint
  583. assert cred._signer is None
  584. assert cred._token_uri is None
  585. assert cred._service_account_email == "foo@example.com"
  586. assert cred._target_audience == "audience"
  587. with pytest.raises(ValueError):
  588. cred.sign_bytes(b"bytes")
  589. @mock.patch(
  590. "google.auth.compute_engine._metadata.get_service_account_info", autospec=True
  591. )
  592. def test_with_target_audience_for_metadata(self, get_service_account_info):
  593. get_service_account_info.return_value = {"email": "foo@example.com"}
  594. cred = credentials.IDTokenCredentials(
  595. mock.Mock(), "audience", use_metadata_identity_endpoint=True
  596. )
  597. cred = cred.with_target_audience("new_audience")
  598. assert cred._target_audience == "new_audience"
  599. assert cred._use_metadata_identity_endpoint
  600. assert cred._signer is None
  601. assert cred._token_uri is None
  602. assert cred._service_account_email == "foo@example.com"
  603. @mock.patch(
  604. "google.auth.compute_engine._metadata.get_service_account_info", autospec=True
  605. )
  606. def test_id_token_with_quota_project(self, get_service_account_info):
  607. get_service_account_info.return_value = {"email": "foo@example.com"}
  608. cred = credentials.IDTokenCredentials(
  609. mock.Mock(), "audience", use_metadata_identity_endpoint=True
  610. )
  611. cred = cred.with_quota_project("project-foo")
  612. assert cred._quota_project_id == "project-foo"
  613. assert cred._use_metadata_identity_endpoint
  614. assert cred._signer is None
  615. assert cred._token_uri is None
  616. assert cred._service_account_email == "foo@example.com"
  617. @mock.patch(
  618. "google.auth.compute_engine._metadata.get_service_account_info", autospec=True
  619. )
  620. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  621. def test_invalid_id_token_from_metadata(self, get, get_service_account_info):
  622. get.return_value = "invalid_id_token"
  623. get_service_account_info.return_value = {"email": "foo@example.com"}
  624. cred = credentials.IDTokenCredentials(
  625. mock.Mock(), "audience", use_metadata_identity_endpoint=True
  626. )
  627. with pytest.raises(ValueError):
  628. cred.refresh(request=mock.Mock())
  629. @mock.patch(
  630. "google.auth.compute_engine._metadata.get_service_account_info", autospec=True
  631. )
  632. @mock.patch("google.auth.compute_engine._metadata.get", autospec=True)
  633. def test_transport_error_from_metadata(self, get, get_service_account_info):
  634. get.side_effect = exceptions.TransportError("transport error")
  635. get_service_account_info.return_value = {"email": "foo@example.com"}
  636. cred = credentials.IDTokenCredentials(
  637. mock.Mock(), "audience", use_metadata_identity_endpoint=True
  638. )
  639. with pytest.raises(exceptions.RefreshError) as excinfo:
  640. cred.refresh(request=mock.Mock())
  641. assert excinfo.match(r"transport error")
  642. def test_get_id_token_from_metadata_constructor(self):
  643. with pytest.raises(ValueError):
  644. credentials.IDTokenCredentials(
  645. mock.Mock(),
  646. "audience",
  647. use_metadata_identity_endpoint=True,
  648. token_uri="token_uri",
  649. )
  650. with pytest.raises(ValueError):
  651. credentials.IDTokenCredentials(
  652. mock.Mock(),
  653. "audience",
  654. use_metadata_identity_endpoint=True,
  655. signer=mock.Mock(),
  656. )
  657. with pytest.raises(ValueError):
  658. credentials.IDTokenCredentials(
  659. mock.Mock(),
  660. "audience",
  661. use_metadata_identity_endpoint=True,
  662. additional_claims={"key", "value"},
  663. )
  664. with pytest.raises(ValueError):
  665. credentials.IDTokenCredentials(
  666. mock.Mock(),
  667. "audience",
  668. use_metadata_identity_endpoint=True,
  669. service_account_email="foo@example.com",
  670. )