test__default.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  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 json
  15. import os
  16. import mock
  17. import pytest
  18. from google.auth import _default
  19. from google.auth import app_engine
  20. from google.auth import aws
  21. from google.auth import compute_engine
  22. from google.auth import credentials
  23. from google.auth import environment_vars
  24. from google.auth import exceptions
  25. from google.auth import external_account
  26. from google.auth import identity_pool
  27. from google.oauth2 import service_account
  28. import google.oauth2.credentials
  29. import yatest.common
  30. DATA_DIR = os.path.join(yatest.common.test_source_path(), "data")
  31. AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json")
  32. with open(AUTHORIZED_USER_FILE) as fh:
  33. AUTHORIZED_USER_FILE_DATA = json.load(fh)
  34. AUTHORIZED_USER_CLOUD_SDK_FILE = os.path.join(
  35. DATA_DIR, "authorized_user_cloud_sdk.json"
  36. )
  37. AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE = os.path.join(
  38. DATA_DIR, "authorized_user_cloud_sdk_with_quota_project_id.json"
  39. )
  40. SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json")
  41. CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json")
  42. with open(SERVICE_ACCOUNT_FILE) as fh:
  43. SERVICE_ACCOUNT_FILE_DATA = json.load(fh)
  44. SUBJECT_TOKEN_TEXT_FILE = os.path.join(DATA_DIR, "external_subject_token.txt")
  45. TOKEN_URL = "https://sts.googleapis.com/v1/token"
  46. AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID"
  47. REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone"
  48. SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
  49. CRED_VERIFICATION_URL = (
  50. "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
  51. )
  52. IDENTITY_POOL_DATA = {
  53. "type": "external_account",
  54. "audience": AUDIENCE,
  55. "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  56. "token_url": TOKEN_URL,
  57. "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE},
  58. }
  59. AWS_DATA = {
  60. "type": "external_account",
  61. "audience": AUDIENCE,
  62. "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
  63. "token_url": TOKEN_URL,
  64. "credential_source": {
  65. "environment_id": "aws1",
  66. "region_url": REGION_URL,
  67. "url": SECURITY_CREDS_URL,
  68. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  69. },
  70. }
  71. MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject)
  72. MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS
  73. def get_project_id_side_effect(self, request=None):
  74. # If no scopes are set, this will always return None.
  75. if not self.scopes:
  76. return None
  77. return mock.sentinel.project_id
  78. LOAD_FILE_PATCH = mock.patch(
  79. "google.auth._default.load_credentials_from_file",
  80. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  81. autospec=True,
  82. )
  83. EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH = mock.patch.object(
  84. external_account.Credentials,
  85. "get_project_id",
  86. side_effect=get_project_id_side_effect,
  87. autospec=True,
  88. )
  89. def test_load_credentials_from_missing_file():
  90. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  91. _default.load_credentials_from_file("")
  92. assert excinfo.match(r"not found")
  93. def test_load_credentials_from_file_invalid_json(tmpdir):
  94. jsonfile = tmpdir.join("invalid.json")
  95. jsonfile.write("{")
  96. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  97. _default.load_credentials_from_file(str(jsonfile))
  98. assert excinfo.match(r"not a valid json file")
  99. def test_load_credentials_from_file_invalid_type(tmpdir):
  100. jsonfile = tmpdir.join("invalid.json")
  101. jsonfile.write(json.dumps({"type": "not-a-real-type"}))
  102. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  103. _default.load_credentials_from_file(str(jsonfile))
  104. assert excinfo.match(r"does not have a valid type")
  105. def test_load_credentials_from_file_authorized_user():
  106. credentials, project_id = _default.load_credentials_from_file(AUTHORIZED_USER_FILE)
  107. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  108. assert project_id is None
  109. def test_load_credentials_from_file_no_type(tmpdir):
  110. # use the client_secrets.json, which is valid json but not a
  111. # loadable credentials type
  112. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  113. _default.load_credentials_from_file(CLIENT_SECRETS_FILE)
  114. assert excinfo.match(r"does not have a valid type")
  115. assert excinfo.match(r"Type is None")
  116. def test_load_credentials_from_file_authorized_user_bad_format(tmpdir):
  117. filename = tmpdir.join("authorized_user_bad.json")
  118. filename.write(json.dumps({"type": "authorized_user"}))
  119. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  120. _default.load_credentials_from_file(str(filename))
  121. assert excinfo.match(r"Failed to load authorized user")
  122. assert excinfo.match(r"missing fields")
  123. def test_load_credentials_from_file_authorized_user_cloud_sdk():
  124. with pytest.warns(UserWarning, match="Cloud SDK"):
  125. credentials, project_id = _default.load_credentials_from_file(
  126. AUTHORIZED_USER_CLOUD_SDK_FILE
  127. )
  128. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  129. assert project_id is None
  130. # No warning if the json file has quota project id.
  131. credentials, project_id = _default.load_credentials_from_file(
  132. AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE
  133. )
  134. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  135. assert project_id is None
  136. def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes():
  137. with pytest.warns(UserWarning, match="Cloud SDK"):
  138. credentials, project_id = _default.load_credentials_from_file(
  139. AUTHORIZED_USER_CLOUD_SDK_FILE,
  140. scopes=["https://www.google.com/calendar/feeds"],
  141. )
  142. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  143. assert project_id is None
  144. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  145. def test_load_credentials_from_file_authorized_user_cloud_sdk_with_quota_project():
  146. credentials, project_id = _default.load_credentials_from_file(
  147. AUTHORIZED_USER_CLOUD_SDK_FILE, quota_project_id="project-foo"
  148. )
  149. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  150. assert project_id is None
  151. assert credentials.quota_project_id == "project-foo"
  152. def test_load_credentials_from_file_service_account():
  153. credentials, project_id = _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE)
  154. assert isinstance(credentials, service_account.Credentials)
  155. assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
  156. def test_load_credentials_from_file_service_account_with_scopes():
  157. credentials, project_id = _default.load_credentials_from_file(
  158. SERVICE_ACCOUNT_FILE, scopes=["https://www.google.com/calendar/feeds"]
  159. )
  160. assert isinstance(credentials, service_account.Credentials)
  161. assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
  162. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  163. def test_load_credentials_from_file_service_account_with_quota_project():
  164. credentials, project_id = _default.load_credentials_from_file(
  165. SERVICE_ACCOUNT_FILE, quota_project_id="project-foo"
  166. )
  167. assert isinstance(credentials, service_account.Credentials)
  168. assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
  169. assert credentials.quota_project_id == "project-foo"
  170. def test_load_credentials_from_file_service_account_bad_format(tmpdir):
  171. filename = tmpdir.join("serivce_account_bad.json")
  172. filename.write(json.dumps({"type": "service_account"}))
  173. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  174. _default.load_credentials_from_file(str(filename))
  175. assert excinfo.match(r"Failed to load service account")
  176. assert excinfo.match(r"missing fields")
  177. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  178. def test_load_credentials_from_file_external_account_identity_pool(
  179. get_project_id, tmpdir
  180. ):
  181. config_file = tmpdir.join("config.json")
  182. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  183. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  184. assert isinstance(credentials, identity_pool.Credentials)
  185. # Since no scopes are specified, the project ID cannot be determined.
  186. assert project_id is None
  187. assert get_project_id.called
  188. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  189. def test_load_credentials_from_file_external_account_aws(get_project_id, tmpdir):
  190. config_file = tmpdir.join("config.json")
  191. config_file.write(json.dumps(AWS_DATA))
  192. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  193. assert isinstance(credentials, aws.Credentials)
  194. # Since no scopes are specified, the project ID cannot be determined.
  195. assert project_id is None
  196. assert get_project_id.called
  197. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  198. def test_load_credentials_from_file_external_account_with_user_and_default_scopes(
  199. get_project_id, tmpdir
  200. ):
  201. config_file = tmpdir.join("config.json")
  202. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  203. credentials, project_id = _default.load_credentials_from_file(
  204. str(config_file),
  205. scopes=["https://www.google.com/calendar/feeds"],
  206. default_scopes=["https://www.googleapis.com/auth/cloud-platform"],
  207. )
  208. assert isinstance(credentials, identity_pool.Credentials)
  209. # Since scopes are specified, the project ID can be determined.
  210. assert project_id is mock.sentinel.project_id
  211. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  212. assert credentials.default_scopes == [
  213. "https://www.googleapis.com/auth/cloud-platform"
  214. ]
  215. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  216. def test_load_credentials_from_file_external_account_with_quota_project(
  217. get_project_id, tmpdir
  218. ):
  219. config_file = tmpdir.join("config.json")
  220. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  221. credentials, project_id = _default.load_credentials_from_file(
  222. str(config_file), quota_project_id="project-foo"
  223. )
  224. assert isinstance(credentials, identity_pool.Credentials)
  225. # Since no scopes are specified, the project ID cannot be determined.
  226. assert project_id is None
  227. assert credentials.quota_project_id == "project-foo"
  228. def test_load_credentials_from_file_external_account_bad_format(tmpdir):
  229. filename = tmpdir.join("external_account_bad.json")
  230. filename.write(json.dumps({"type": "external_account"}))
  231. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  232. _default.load_credentials_from_file(str(filename))
  233. assert excinfo.match(
  234. "Failed to load external account credentials from {}".format(str(filename))
  235. )
  236. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  237. def test_load_credentials_from_file_external_account_explicit_request(
  238. get_project_id, tmpdir
  239. ):
  240. config_file = tmpdir.join("config.json")
  241. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  242. credentials, project_id = _default.load_credentials_from_file(
  243. str(config_file),
  244. request=mock.sentinel.request,
  245. scopes=["https://www.googleapis.com/auth/cloud-platform"],
  246. )
  247. assert isinstance(credentials, identity_pool.Credentials)
  248. # Since scopes are specified, the project ID can be determined.
  249. assert project_id is mock.sentinel.project_id
  250. get_project_id.assert_called_with(credentials, request=mock.sentinel.request)
  251. @mock.patch.dict(os.environ, {}, clear=True)
  252. def test__get_explicit_environ_credentials_no_env():
  253. assert _default._get_explicit_environ_credentials() == (None, None)
  254. @LOAD_FILE_PATCH
  255. def test__get_explicit_environ_credentials(load, monkeypatch):
  256. monkeypatch.setenv(environment_vars.CREDENTIALS, "filename")
  257. credentials, project_id = _default._get_explicit_environ_credentials()
  258. assert credentials is MOCK_CREDENTIALS
  259. assert project_id is mock.sentinel.project_id
  260. load.assert_called_with("filename")
  261. @LOAD_FILE_PATCH
  262. def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch):
  263. load.return_value = MOCK_CREDENTIALS, None
  264. monkeypatch.setenv(environment_vars.CREDENTIALS, "filename")
  265. credentials, project_id = _default._get_explicit_environ_credentials()
  266. assert credentials is MOCK_CREDENTIALS
  267. assert project_id is None
  268. @mock.patch(
  269. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  270. )
  271. @mock.patch("google.auth._default._get_gcloud_sdk_credentials", autospec=True)
  272. def test__get_explicit_environ_credentials_fallback_to_gcloud(
  273. get_gcloud_creds, get_adc_path, monkeypatch
  274. ):
  275. # Set explicit credentials path to cloud sdk credentials path.
  276. get_adc_path.return_value = "filename"
  277. monkeypatch.setenv(environment_vars.CREDENTIALS, "filename")
  278. _default._get_explicit_environ_credentials()
  279. # Check we fall back to cloud sdk flow since explicit credentials path is
  280. # cloud sdk credentials path
  281. get_gcloud_creds.assert_called_once()
  282. @LOAD_FILE_PATCH
  283. @mock.patch(
  284. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  285. )
  286. def test__get_gcloud_sdk_credentials(get_adc_path, load):
  287. get_adc_path.return_value = SERVICE_ACCOUNT_FILE
  288. credentials, project_id = _default._get_gcloud_sdk_credentials()
  289. assert credentials is MOCK_CREDENTIALS
  290. assert project_id is mock.sentinel.project_id
  291. load.assert_called_with(SERVICE_ACCOUNT_FILE)
  292. @mock.patch(
  293. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  294. )
  295. def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir):
  296. non_existent = tmpdir.join("non-existent")
  297. get_adc_path.return_value = str(non_existent)
  298. credentials, project_id = _default._get_gcloud_sdk_credentials()
  299. assert credentials is None
  300. assert project_id is None
  301. @mock.patch(
  302. "google.auth._cloud_sdk.get_project_id",
  303. return_value=mock.sentinel.project_id,
  304. autospec=True,
  305. )
  306. @mock.patch("os.path.isfile", return_value=True, autospec=True)
  307. @LOAD_FILE_PATCH
  308. def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project_id):
  309. # Don't return a project ID from load file, make the function check
  310. # the Cloud SDK project.
  311. load.return_value = MOCK_CREDENTIALS, None
  312. credentials, project_id = _default._get_gcloud_sdk_credentials()
  313. assert credentials == MOCK_CREDENTIALS
  314. assert project_id == mock.sentinel.project_id
  315. assert get_project_id.called
  316. @mock.patch("google.auth._cloud_sdk.get_project_id", return_value=None, autospec=True)
  317. @mock.patch("os.path.isfile", return_value=True)
  318. @LOAD_FILE_PATCH
  319. def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_project_id):
  320. # Don't return a project ID from load file, make the function check
  321. # the Cloud SDK project.
  322. load.return_value = MOCK_CREDENTIALS, None
  323. credentials, project_id = _default._get_gcloud_sdk_credentials()
  324. assert credentials == MOCK_CREDENTIALS
  325. assert project_id is None
  326. assert get_project_id.called
  327. class _AppIdentityModule(object):
  328. """The interface of the App Idenity app engine module.
  329. See https://cloud.google.com/appengine/docs/standard/python/refdocs\
  330. /google.appengine.api.app_identity.app_identity
  331. """
  332. def get_application_id(self):
  333. raise NotImplementedError()
  334. @pytest.fixture
  335. def app_identity(monkeypatch):
  336. """Mocks the app_identity module for google.auth.app_engine."""
  337. app_identity_module = mock.create_autospec(_AppIdentityModule, instance=True)
  338. monkeypatch.setattr(app_engine, "app_identity", app_identity_module)
  339. yield app_identity_module
  340. @mock.patch.dict(os.environ)
  341. def test__get_gae_credentials_gen1(app_identity):
  342. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
  343. app_identity.get_application_id.return_value = mock.sentinel.project
  344. credentials, project_id = _default._get_gae_credentials()
  345. assert isinstance(credentials, app_engine.Credentials)
  346. assert project_id == mock.sentinel.project
  347. @mock.patch.dict(os.environ)
  348. def test__get_gae_credentials_gen2():
  349. os.environ["GAE_RUNTIME"] = "python37"
  350. credentials, project_id = _default._get_gae_credentials()
  351. assert credentials is None
  352. assert project_id is None
  353. @mock.patch.dict(os.environ)
  354. def test__get_gae_credentials_gen2_backwards_compat():
  355. # compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME
  356. # for backwards compatibility with code that relies on it
  357. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37"
  358. os.environ["GAE_RUNTIME"] = "python37"
  359. credentials, project_id = _default._get_gae_credentials()
  360. assert credentials is None
  361. assert project_id is None
  362. def test__get_gae_credentials_env_unset():
  363. assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
  364. assert "GAE_RUNTIME" not in os.environ
  365. credentials, project_id = _default._get_gae_credentials()
  366. assert credentials is None
  367. assert project_id is None
  368. @mock.patch.dict(os.environ)
  369. def test__get_gae_credentials_no_app_engine():
  370. # test both with and without LEGACY_APPENGINE_RUNTIME setting
  371. assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
  372. import sys
  373. with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}):
  374. credentials, project_id = _default._get_gae_credentials()
  375. assert credentials is None
  376. assert project_id is None
  377. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
  378. credentials, project_id = _default._get_gae_credentials()
  379. assert credentials is None
  380. assert project_id is None
  381. @mock.patch.dict(os.environ)
  382. @mock.patch.object(app_engine, "app_identity", new=None)
  383. def test__get_gae_credentials_no_apis():
  384. # test both with and without LEGACY_APPENGINE_RUNTIME setting
  385. assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
  386. credentials, project_id = _default._get_gae_credentials()
  387. assert credentials is None
  388. assert project_id is None
  389. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
  390. credentials, project_id = _default._get_gae_credentials()
  391. assert credentials is None
  392. assert project_id is None
  393. @mock.patch(
  394. "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True
  395. )
  396. @mock.patch(
  397. "google.auth.compute_engine._metadata.get_project_id",
  398. return_value="example-project",
  399. autospec=True,
  400. )
  401. def test__get_gce_credentials(unused_get, unused_ping):
  402. credentials, project_id = _default._get_gce_credentials()
  403. assert isinstance(credentials, compute_engine.Credentials)
  404. assert project_id == "example-project"
  405. @mock.patch(
  406. "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True
  407. )
  408. def test__get_gce_credentials_no_ping(unused_ping):
  409. credentials, project_id = _default._get_gce_credentials()
  410. assert credentials is None
  411. assert project_id is None
  412. @mock.patch(
  413. "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True
  414. )
  415. @mock.patch(
  416. "google.auth.compute_engine._metadata.get_project_id",
  417. side_effect=exceptions.TransportError(),
  418. autospec=True,
  419. )
  420. def test__get_gce_credentials_no_project_id(unused_get, unused_ping):
  421. credentials, project_id = _default._get_gce_credentials()
  422. assert isinstance(credentials, compute_engine.Credentials)
  423. assert project_id is None
  424. def test__get_gce_credentials_no_compute_engine():
  425. import sys
  426. with mock.patch.dict("sys.modules"):
  427. sys.modules["google.auth.compute_engine"] = None
  428. credentials, project_id = _default._get_gce_credentials()
  429. assert credentials is None
  430. assert project_id is None
  431. @mock.patch(
  432. "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True
  433. )
  434. def test__get_gce_credentials_explicit_request(ping):
  435. _default._get_gce_credentials(mock.sentinel.request)
  436. ping.assert_called_with(request=mock.sentinel.request)
  437. @mock.patch(
  438. "google.auth._default._get_explicit_environ_credentials",
  439. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  440. autospec=True,
  441. )
  442. def test_default_early_out(unused_get):
  443. assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id)
  444. @mock.patch(
  445. "google.auth._default._get_explicit_environ_credentials",
  446. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  447. autospec=True,
  448. )
  449. def test_default_explict_project_id(unused_get, monkeypatch):
  450. monkeypatch.setenv(environment_vars.PROJECT, "explicit-env")
  451. assert _default.default() == (MOCK_CREDENTIALS, "explicit-env")
  452. @mock.patch(
  453. "google.auth._default._get_explicit_environ_credentials",
  454. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  455. autospec=True,
  456. )
  457. def test_default_explict_legacy_project_id(unused_get, monkeypatch):
  458. monkeypatch.setenv(environment_vars.LEGACY_PROJECT, "explicit-env")
  459. assert _default.default() == (MOCK_CREDENTIALS, "explicit-env")
  460. @mock.patch("logging.Logger.warning", autospec=True)
  461. @mock.patch(
  462. "google.auth._default._get_explicit_environ_credentials",
  463. return_value=(MOCK_CREDENTIALS, None),
  464. autospec=True,
  465. )
  466. @mock.patch(
  467. "google.auth._default._get_gcloud_sdk_credentials",
  468. return_value=(MOCK_CREDENTIALS, None),
  469. autospec=True,
  470. )
  471. @mock.patch(
  472. "google.auth._default._get_gae_credentials",
  473. return_value=(MOCK_CREDENTIALS, None),
  474. autospec=True,
  475. )
  476. @mock.patch(
  477. "google.auth._default._get_gce_credentials",
  478. return_value=(MOCK_CREDENTIALS, None),
  479. autospec=True,
  480. )
  481. def test_default_without_project_id(
  482. unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning
  483. ):
  484. assert _default.default() == (MOCK_CREDENTIALS, None)
  485. logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY)
  486. @mock.patch(
  487. "google.auth._default._get_explicit_environ_credentials",
  488. return_value=(None, None),
  489. autospec=True,
  490. )
  491. @mock.patch(
  492. "google.auth._default._get_gcloud_sdk_credentials",
  493. return_value=(None, None),
  494. autospec=True,
  495. )
  496. @mock.patch(
  497. "google.auth._default._get_gae_credentials",
  498. return_value=(None, None),
  499. autospec=True,
  500. )
  501. @mock.patch(
  502. "google.auth._default._get_gce_credentials",
  503. return_value=(None, None),
  504. autospec=True,
  505. )
  506. def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit):
  507. with pytest.raises(exceptions.DefaultCredentialsError):
  508. assert _default.default()
  509. @mock.patch(
  510. "google.auth._default._get_explicit_environ_credentials",
  511. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  512. autospec=True,
  513. )
  514. @mock.patch(
  515. "google.auth.credentials.with_scopes_if_required",
  516. return_value=MOCK_CREDENTIALS,
  517. autospec=True,
  518. )
  519. def test_default_scoped(with_scopes, unused_get):
  520. scopes = ["one", "two"]
  521. credentials, project_id = _default.default(scopes=scopes)
  522. assert credentials == with_scopes.return_value
  523. assert project_id == mock.sentinel.project_id
  524. with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes, default_scopes=None)
  525. @mock.patch(
  526. "google.auth._default._get_explicit_environ_credentials",
  527. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  528. autospec=True,
  529. )
  530. def test_default_quota_project(with_quota_project):
  531. credentials, project_id = _default.default(quota_project_id="project-foo")
  532. MOCK_CREDENTIALS.with_quota_project.assert_called_once_with("project-foo")
  533. assert project_id == mock.sentinel.project_id
  534. @mock.patch(
  535. "google.auth._default._get_explicit_environ_credentials",
  536. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  537. autospec=True,
  538. )
  539. def test_default_no_app_engine_compute_engine_module(unused_get):
  540. """
  541. google.auth.compute_engine and google.auth.app_engine are both optional
  542. to allow not including them when using this package. This verifies
  543. that default fails gracefully if these modules are absent
  544. """
  545. import sys
  546. with mock.patch.dict("sys.modules"):
  547. sys.modules["google.auth.compute_engine"] = None
  548. sys.modules["google.auth.app_engine"] = None
  549. assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id)
  550. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  551. def test_default_environ_external_credentials(get_project_id, monkeypatch, tmpdir):
  552. config_file = tmpdir.join("config.json")
  553. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  554. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  555. credentials, project_id = _default.default()
  556. assert isinstance(credentials, identity_pool.Credentials)
  557. # Without scopes, project ID cannot be determined.
  558. assert project_id is None
  559. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  560. def test_default_environ_external_credentials_with_user_and_default_scopes_and_quota_project_id(
  561. get_project_id, monkeypatch, tmpdir
  562. ):
  563. config_file = tmpdir.join("config.json")
  564. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  565. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  566. credentials, project_id = _default.default(
  567. scopes=["https://www.google.com/calendar/feeds"],
  568. default_scopes=["https://www.googleapis.com/auth/cloud-platform"],
  569. quota_project_id="project-foo",
  570. )
  571. assert isinstance(credentials, identity_pool.Credentials)
  572. assert project_id is mock.sentinel.project_id
  573. assert credentials.quota_project_id == "project-foo"
  574. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  575. assert credentials.default_scopes == [
  576. "https://www.googleapis.com/auth/cloud-platform"
  577. ]
  578. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  579. def test_default_environ_external_credentials_explicit_request_with_scopes(
  580. get_project_id, monkeypatch, tmpdir
  581. ):
  582. config_file = tmpdir.join("config.json")
  583. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  584. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  585. credentials, project_id = _default.default(
  586. request=mock.sentinel.request,
  587. scopes=["https://www.googleapis.com/auth/cloud-platform"],
  588. )
  589. assert isinstance(credentials, identity_pool.Credentials)
  590. assert project_id is mock.sentinel.project_id
  591. # default() will initialize new credentials via with_scopes_if_required
  592. # and potentially with_quota_project.
  593. # As a result the caller of get_project_id() will not match the returned
  594. # credentials.
  595. get_project_id.assert_called_with(mock.ANY, request=mock.sentinel.request)
  596. def test_default_environ_external_credentials_bad_format(monkeypatch, tmpdir):
  597. filename = tmpdir.join("external_account_bad.json")
  598. filename.write(json.dumps({"type": "external_account"}))
  599. monkeypatch.setenv(environment_vars.CREDENTIALS, str(filename))
  600. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  601. _default.default()
  602. assert excinfo.match(
  603. "Failed to load external account credentials from {}".format(str(filename))
  604. )