test__default.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  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 # type: ignore
  18. from google.auth import _default
  19. from google.auth import api_key
  20. from google.auth import app_engine
  21. from google.auth import aws
  22. from google.auth import compute_engine
  23. from google.auth import credentials
  24. from google.auth import environment_vars
  25. from google.auth import exceptions
  26. from google.auth import external_account
  27. from google.auth import external_account_authorized_user
  28. from google.auth import identity_pool
  29. from google.auth import impersonated_credentials
  30. from google.auth import pluggable
  31. from google.oauth2 import gdch_credentials
  32. from google.oauth2 import service_account
  33. import google.oauth2.credentials
  34. import yatest.common as yc
  35. DATA_DIR = os.path.join(os.path.dirname(yc.source_path(__file__)), "data")
  36. AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json")
  37. with open(AUTHORIZED_USER_FILE) as fh:
  38. AUTHORIZED_USER_FILE_DATA = json.load(fh)
  39. AUTHORIZED_USER_CLOUD_SDK_FILE = os.path.join(
  40. DATA_DIR, "authorized_user_cloud_sdk.json"
  41. )
  42. AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE = os.path.join(
  43. DATA_DIR, "authorized_user_cloud_sdk_with_quota_project_id.json"
  44. )
  45. SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json")
  46. CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json")
  47. GDCH_SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "gdch_service_account.json")
  48. with open(SERVICE_ACCOUNT_FILE) as fh:
  49. SERVICE_ACCOUNT_FILE_DATA = json.load(fh)
  50. SUBJECT_TOKEN_TEXT_FILE = os.path.join(DATA_DIR, "external_subject_token.txt")
  51. TOKEN_URL = "https://sts.googleapis.com/v1/token"
  52. AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID"
  53. WORKFORCE_AUDIENCE = (
  54. "//iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID"
  55. )
  56. WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER"
  57. REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone"
  58. SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
  59. CRED_VERIFICATION_URL = (
  60. "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
  61. )
  62. IDENTITY_POOL_DATA = {
  63. "type": "external_account",
  64. "audience": AUDIENCE,
  65. "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  66. "token_url": TOKEN_URL,
  67. "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE},
  68. }
  69. PLUGGABLE_DATA = {
  70. "type": "external_account",
  71. "audience": AUDIENCE,
  72. "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  73. "token_url": TOKEN_URL,
  74. "credential_source": {"executable": {"command": "command"}},
  75. }
  76. AWS_DATA = {
  77. "type": "external_account",
  78. "audience": AUDIENCE,
  79. "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
  80. "token_url": TOKEN_URL,
  81. "credential_source": {
  82. "environment_id": "aws1",
  83. "region_url": REGION_URL,
  84. "url": SECURITY_CREDS_URL,
  85. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  86. },
  87. }
  88. SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com"
  89. SERVICE_ACCOUNT_IMPERSONATION_URL = (
  90. "https://us-east1-iamcredentials.googleapis.com/v1/projects/-"
  91. + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL)
  92. )
  93. IMPERSONATED_IDENTITY_POOL_DATA = {
  94. "type": "external_account",
  95. "audience": AUDIENCE,
  96. "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  97. "token_url": TOKEN_URL,
  98. "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE},
  99. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  100. }
  101. IMPERSONATED_AWS_DATA = {
  102. "type": "external_account",
  103. "audience": AUDIENCE,
  104. "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request",
  105. "token_url": TOKEN_URL,
  106. "credential_source": {
  107. "environment_id": "aws1",
  108. "region_url": REGION_URL,
  109. "url": SECURITY_CREDS_URL,
  110. "regional_cred_verification_url": CRED_VERIFICATION_URL,
  111. },
  112. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  113. }
  114. IDENTITY_POOL_WORKFORCE_DATA = {
  115. "type": "external_account",
  116. "audience": WORKFORCE_AUDIENCE,
  117. "subject_token_type": "urn:ietf:params:oauth:token-type:id_token",
  118. "token_url": TOKEN_URL,
  119. "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE},
  120. "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT,
  121. }
  122. IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA = {
  123. "type": "external_account",
  124. "audience": WORKFORCE_AUDIENCE,
  125. "subject_token_type": "urn:ietf:params:oauth:token-type:id_token",
  126. "token_url": TOKEN_URL,
  127. "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE},
  128. "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL,
  129. "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT,
  130. }
  131. IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join(
  132. DATA_DIR, "impersonated_service_account_authorized_user_source.json"
  133. )
  134. IMPERSONATED_SERVICE_ACCOUNT_WITH_QUOTA_PROJECT_FILE = os.path.join(
  135. DATA_DIR, "impersonated_service_account_with_quota_project.json"
  136. )
  137. IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE = os.path.join(
  138. DATA_DIR, "impersonated_service_account_service_account_source.json"
  139. )
  140. EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE = os.path.join(
  141. DATA_DIR, "external_account_authorized_user.json"
  142. )
  143. EXTERNAL_ACCOUNT_AUTHORIZED_USER_NON_GDU_FILE = os.path.join(
  144. DATA_DIR, "external_account_authorized_user_non_gdu.json"
  145. )
  146. MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject)
  147. MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS
  148. def get_project_id_side_effect(self, request=None):
  149. # If no scopes are set, this will always return None.
  150. if not self.scopes:
  151. return None
  152. return mock.sentinel.project_id
  153. LOAD_FILE_PATCH = mock.patch(
  154. "google.auth._default.load_credentials_from_file",
  155. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  156. autospec=True,
  157. )
  158. EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH = mock.patch.object(
  159. external_account.Credentials,
  160. "get_project_id",
  161. side_effect=get_project_id_side_effect,
  162. autospec=True,
  163. )
  164. def test_load_credentials_from_missing_file():
  165. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  166. _default.load_credentials_from_file("")
  167. assert excinfo.match(r"not found")
  168. def test_load_credentials_from_dict_non_dict_object():
  169. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  170. _default.load_credentials_from_dict("")
  171. assert excinfo.match(r"dict type was expected")
  172. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  173. _default.load_credentials_from_dict(None)
  174. assert excinfo.match(r"dict type was expected")
  175. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  176. _default.load_credentials_from_dict(1)
  177. assert excinfo.match(r"dict type was expected")
  178. def test_load_credentials_from_dict_authorized_user():
  179. credentials, project_id = _default.load_credentials_from_dict(
  180. AUTHORIZED_USER_FILE_DATA
  181. )
  182. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  183. assert project_id is None
  184. def test_load_credentials_from_file_invalid_json(tmpdir):
  185. jsonfile = tmpdir.join("invalid.json")
  186. jsonfile.write("{")
  187. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  188. _default.load_credentials_from_file(str(jsonfile))
  189. assert excinfo.match(r"not a valid json file")
  190. def test_load_credentials_from_file_invalid_type(tmpdir):
  191. jsonfile = tmpdir.join("invalid.json")
  192. jsonfile.write(json.dumps({"type": "not-a-real-type"}))
  193. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  194. _default.load_credentials_from_file(str(jsonfile))
  195. assert excinfo.match(r"does not have a valid type")
  196. def test_load_credentials_from_file_authorized_user():
  197. credentials, project_id = _default.load_credentials_from_file(AUTHORIZED_USER_FILE)
  198. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  199. assert project_id is None
  200. def test_load_credentials_from_file_no_type(tmpdir):
  201. # use the client_secrets.json, which is valid json but not a
  202. # loadable credentials type
  203. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  204. _default.load_credentials_from_file(CLIENT_SECRETS_FILE)
  205. assert excinfo.match(r"does not have a valid type")
  206. assert excinfo.match(r"Type is None")
  207. def test_load_credentials_from_file_authorized_user_bad_format(tmpdir):
  208. filename = tmpdir.join("authorized_user_bad.json")
  209. filename.write(json.dumps({"type": "authorized_user"}))
  210. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  211. _default.load_credentials_from_file(str(filename))
  212. assert excinfo.match(r"Failed to load authorized user")
  213. assert excinfo.match(r"missing fields")
  214. def test_load_credentials_from_file_authorized_user_cloud_sdk():
  215. with pytest.warns(UserWarning, match="Cloud SDK"):
  216. credentials, project_id = _default.load_credentials_from_file(
  217. AUTHORIZED_USER_CLOUD_SDK_FILE
  218. )
  219. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  220. assert project_id is None
  221. # No warning if the json file has quota project id.
  222. credentials, project_id = _default.load_credentials_from_file(
  223. AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE
  224. )
  225. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  226. assert project_id is None
  227. def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes():
  228. with pytest.warns(UserWarning, match="Cloud SDK"):
  229. credentials, project_id = _default.load_credentials_from_file(
  230. AUTHORIZED_USER_CLOUD_SDK_FILE,
  231. scopes=["https://www.google.com/calendar/feeds"],
  232. )
  233. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  234. assert project_id is None
  235. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  236. def test_load_credentials_from_file_authorized_user_cloud_sdk_with_quota_project():
  237. credentials, project_id = _default.load_credentials_from_file(
  238. AUTHORIZED_USER_CLOUD_SDK_FILE, quota_project_id="project-foo"
  239. )
  240. assert isinstance(credentials, google.oauth2.credentials.Credentials)
  241. assert project_id is None
  242. assert credentials.quota_project_id == "project-foo"
  243. def test_load_credentials_from_file_service_account():
  244. credentials, project_id = _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE)
  245. assert isinstance(credentials, service_account.Credentials)
  246. assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
  247. def test_load_credentials_from_file_service_account_with_scopes():
  248. credentials, project_id = _default.load_credentials_from_file(
  249. SERVICE_ACCOUNT_FILE, scopes=["https://www.google.com/calendar/feeds"]
  250. )
  251. assert isinstance(credentials, service_account.Credentials)
  252. assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
  253. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  254. def test_load_credentials_from_file_service_account_with_quota_project():
  255. credentials, project_id = _default.load_credentials_from_file(
  256. SERVICE_ACCOUNT_FILE, quota_project_id="project-foo"
  257. )
  258. assert isinstance(credentials, service_account.Credentials)
  259. assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
  260. assert credentials.quota_project_id == "project-foo"
  261. def test_load_credentials_from_file_service_account_bad_format(tmpdir):
  262. filename = tmpdir.join("serivce_account_bad.json")
  263. filename.write(json.dumps({"type": "service_account"}))
  264. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  265. _default.load_credentials_from_file(str(filename))
  266. assert excinfo.match(r"Failed to load service account")
  267. assert excinfo.match(r"missing fields")
  268. def test_load_credentials_from_file_impersonated_with_authorized_user_source():
  269. credentials, project_id = _default.load_credentials_from_file(
  270. IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE
  271. )
  272. assert isinstance(credentials, impersonated_credentials.Credentials)
  273. assert isinstance(
  274. credentials._source_credentials, google.oauth2.credentials.Credentials
  275. )
  276. assert credentials.service_account_email == "service-account-target@example.com"
  277. assert credentials._delegates == ["service-account-delegate@example.com"]
  278. assert not credentials._quota_project_id
  279. assert not credentials._target_scopes
  280. assert project_id is None
  281. def test_load_credentials_from_file_impersonated_with_quota_project():
  282. credentials, _ = _default.load_credentials_from_file(
  283. IMPERSONATED_SERVICE_ACCOUNT_WITH_QUOTA_PROJECT_FILE
  284. )
  285. assert isinstance(credentials, impersonated_credentials.Credentials)
  286. assert credentials._quota_project_id == "quota_project"
  287. def test_load_credentials_from_file_impersonated_with_service_account_source():
  288. credentials, _ = _default.load_credentials_from_file(
  289. IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE
  290. )
  291. assert isinstance(credentials, impersonated_credentials.Credentials)
  292. assert isinstance(credentials._source_credentials, service_account.Credentials)
  293. assert not credentials._quota_project_id
  294. def test_load_credentials_from_file_impersonated_passing_quota_project():
  295. credentials, _ = _default.load_credentials_from_file(
  296. IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE,
  297. quota_project_id="new_quota_project",
  298. )
  299. assert credentials._quota_project_id == "new_quota_project"
  300. def test_load_credentials_from_file_impersonated_passing_scopes():
  301. credentials, _ = _default.load_credentials_from_file(
  302. IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE,
  303. scopes=["scope1", "scope2"],
  304. )
  305. assert credentials._target_scopes == ["scope1", "scope2"]
  306. def test_load_credentials_from_file_impersonated_wrong_target_principal(tmpdir):
  307. with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE) as fh:
  308. impersonated_credentials_info = json.load(fh)
  309. impersonated_credentials_info[
  310. "service_account_impersonation_url"
  311. ] = "something_wrong"
  312. jsonfile = tmpdir.join("invalid.json")
  313. jsonfile.write(json.dumps(impersonated_credentials_info))
  314. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  315. _default.load_credentials_from_file(str(jsonfile))
  316. assert excinfo.match(r"Cannot extract target principal")
  317. def test_load_credentials_from_file_impersonated_wrong_source_type(tmpdir):
  318. with open(IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE) as fh:
  319. impersonated_credentials_info = json.load(fh)
  320. impersonated_credentials_info["source_credentials"]["type"] = "external_account"
  321. jsonfile = tmpdir.join("invalid.json")
  322. jsonfile.write(json.dumps(impersonated_credentials_info))
  323. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  324. _default.load_credentials_from_file(str(jsonfile))
  325. assert excinfo.match(r"source credential of type external_account is not supported")
  326. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  327. def test_load_credentials_from_file_external_account_identity_pool(
  328. get_project_id, tmpdir
  329. ):
  330. config_file = tmpdir.join("config.json")
  331. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  332. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  333. assert isinstance(credentials, identity_pool.Credentials)
  334. # Since no scopes are specified, the project ID cannot be determined.
  335. assert project_id is None
  336. assert get_project_id.called
  337. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  338. def test_load_credentials_from_file_external_account_aws(get_project_id, tmpdir):
  339. config_file = tmpdir.join("config.json")
  340. config_file.write(json.dumps(AWS_DATA))
  341. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  342. assert isinstance(credentials, aws.Credentials)
  343. # Since no scopes are specified, the project ID cannot be determined.
  344. assert project_id is None
  345. assert get_project_id.called
  346. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  347. def test_load_credentials_from_file_external_account_identity_pool_impersonated(
  348. get_project_id, tmpdir
  349. ):
  350. config_file = tmpdir.join("config.json")
  351. config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA))
  352. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  353. assert isinstance(credentials, identity_pool.Credentials)
  354. assert not credentials.is_user
  355. assert not credentials.is_workforce_pool
  356. # Since no scopes are specified, the project ID cannot be determined.
  357. assert project_id is None
  358. assert get_project_id.called
  359. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  360. def test_load_credentials_from_file_external_account_aws_impersonated(
  361. get_project_id, tmpdir
  362. ):
  363. config_file = tmpdir.join("config.json")
  364. config_file.write(json.dumps(IMPERSONATED_AWS_DATA))
  365. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  366. assert isinstance(credentials, aws.Credentials)
  367. assert not credentials.is_user
  368. assert not credentials.is_workforce_pool
  369. # Since no scopes are specified, the project ID cannot be determined.
  370. assert project_id is None
  371. assert get_project_id.called
  372. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  373. def test_load_credentials_from_file_external_account_workforce(get_project_id, tmpdir):
  374. config_file = tmpdir.join("config.json")
  375. config_file.write(json.dumps(IDENTITY_POOL_WORKFORCE_DATA))
  376. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  377. assert isinstance(credentials, identity_pool.Credentials)
  378. assert credentials.is_user
  379. assert credentials.is_workforce_pool
  380. # Since no scopes are specified, the project ID cannot be determined.
  381. assert project_id is None
  382. assert get_project_id.called
  383. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  384. def test_load_credentials_from_file_external_account_workforce_impersonated(
  385. get_project_id, tmpdir
  386. ):
  387. config_file = tmpdir.join("config.json")
  388. config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA))
  389. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  390. assert isinstance(credentials, identity_pool.Credentials)
  391. assert not credentials.is_user
  392. assert credentials.is_workforce_pool
  393. # Since no scopes are specified, the project ID cannot be determined.
  394. assert project_id is None
  395. assert get_project_id.called
  396. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  397. def test_load_credentials_from_file_external_account_with_user_and_default_scopes(
  398. get_project_id, tmpdir
  399. ):
  400. config_file = tmpdir.join("config.json")
  401. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  402. credentials, project_id = _default.load_credentials_from_file(
  403. str(config_file),
  404. scopes=["https://www.google.com/calendar/feeds"],
  405. default_scopes=["https://www.googleapis.com/auth/cloud-platform"],
  406. )
  407. assert isinstance(credentials, identity_pool.Credentials)
  408. # Since scopes are specified, the project ID can be determined.
  409. assert project_id is mock.sentinel.project_id
  410. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  411. assert credentials.default_scopes == [
  412. "https://www.googleapis.com/auth/cloud-platform"
  413. ]
  414. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  415. def test_load_credentials_from_file_external_account_with_quota_project(
  416. get_project_id, tmpdir
  417. ):
  418. config_file = tmpdir.join("config.json")
  419. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  420. credentials, project_id = _default.load_credentials_from_file(
  421. str(config_file), quota_project_id="project-foo"
  422. )
  423. assert isinstance(credentials, identity_pool.Credentials)
  424. # Since no scopes are specified, the project ID cannot be determined.
  425. assert project_id is None
  426. assert credentials.quota_project_id == "project-foo"
  427. def test_load_credentials_from_file_external_account_bad_format(tmpdir):
  428. filename = tmpdir.join("external_account_bad.json")
  429. filename.write(json.dumps({"type": "external_account"}))
  430. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  431. _default.load_credentials_from_file(str(filename))
  432. assert excinfo.match(
  433. "Failed to load external account credentials from {}".format(str(filename))
  434. )
  435. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  436. def test_load_credentials_from_file_external_account_explicit_request(
  437. get_project_id, tmpdir
  438. ):
  439. config_file = tmpdir.join("config.json")
  440. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  441. credentials, project_id = _default.load_credentials_from_file(
  442. str(config_file),
  443. request=mock.sentinel.request,
  444. scopes=["https://www.googleapis.com/auth/cloud-platform"],
  445. )
  446. assert isinstance(credentials, identity_pool.Credentials)
  447. # Since scopes are specified, the project ID can be determined.
  448. assert project_id is mock.sentinel.project_id
  449. get_project_id.assert_called_with(credentials, request=mock.sentinel.request)
  450. @mock.patch.dict(os.environ, {}, clear=True)
  451. def test__get_explicit_environ_credentials_no_env():
  452. assert _default._get_explicit_environ_credentials() == (None, None)
  453. def test_load_credentials_from_file_external_account_authorized_user():
  454. credentials, project_id = _default.load_credentials_from_file(
  455. EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE, request=mock.sentinel.request
  456. )
  457. assert isinstance(credentials, external_account_authorized_user.Credentials)
  458. assert project_id is None
  459. def test_load_credentials_from_file_external_account_authorized_user_non_gdu():
  460. credentials, _ = _default.load_credentials_from_file(
  461. EXTERNAL_ACCOUNT_AUTHORIZED_USER_NON_GDU_FILE, request=mock.sentinel.request
  462. )
  463. assert isinstance(credentials, external_account_authorized_user.Credentials)
  464. assert credentials.universe_domain == "fake_universe_domain"
  465. def test_load_credentials_from_file_external_account_authorized_user_bad_format(tmpdir):
  466. filename = tmpdir.join("external_account_authorized_user_bad.json")
  467. filename.write(json.dumps({"type": "external_account_authorized_user"}))
  468. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  469. _default.load_credentials_from_file(str(filename))
  470. assert excinfo.match(
  471. "Failed to load external account authorized user credentials from {}".format(
  472. str(filename)
  473. )
  474. )
  475. @pytest.mark.parametrize("quota_project_id", [None, "project-foo"])
  476. @LOAD_FILE_PATCH
  477. def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch):
  478. monkeypatch.setenv(environment_vars.CREDENTIALS, "filename")
  479. credentials, project_id = _default._get_explicit_environ_credentials(
  480. quota_project_id=quota_project_id
  481. )
  482. assert credentials is MOCK_CREDENTIALS
  483. assert project_id is mock.sentinel.project_id
  484. load.assert_called_with("filename", quota_project_id=quota_project_id)
  485. @LOAD_FILE_PATCH
  486. def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch):
  487. load.return_value = MOCK_CREDENTIALS, None
  488. monkeypatch.setenv(environment_vars.CREDENTIALS, "filename")
  489. credentials, project_id = _default._get_explicit_environ_credentials()
  490. assert credentials is MOCK_CREDENTIALS
  491. assert project_id is None
  492. @pytest.mark.parametrize("quota_project_id", [None, "project-foo"])
  493. @mock.patch(
  494. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  495. )
  496. @mock.patch("google.auth._default._get_gcloud_sdk_credentials", autospec=True)
  497. def test__get_explicit_environ_credentials_fallback_to_gcloud(
  498. get_gcloud_creds, get_adc_path, quota_project_id, monkeypatch
  499. ):
  500. # Set explicit credentials path to cloud sdk credentials path.
  501. get_adc_path.return_value = "filename"
  502. monkeypatch.setenv(environment_vars.CREDENTIALS, "filename")
  503. _default._get_explicit_environ_credentials(quota_project_id=quota_project_id)
  504. # Check we fall back to cloud sdk flow since explicit credentials path is
  505. # cloud sdk credentials path
  506. get_gcloud_creds.assert_called_with(quota_project_id=quota_project_id)
  507. @pytest.mark.parametrize("quota_project_id", [None, "project-foo"])
  508. @LOAD_FILE_PATCH
  509. @mock.patch(
  510. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  511. )
  512. def test__get_gcloud_sdk_credentials(get_adc_path, load, quota_project_id):
  513. get_adc_path.return_value = SERVICE_ACCOUNT_FILE
  514. credentials, project_id = _default._get_gcloud_sdk_credentials(
  515. quota_project_id=quota_project_id
  516. )
  517. assert credentials is MOCK_CREDENTIALS
  518. assert project_id is mock.sentinel.project_id
  519. load.assert_called_with(SERVICE_ACCOUNT_FILE, quota_project_id=quota_project_id)
  520. @mock.patch(
  521. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  522. )
  523. def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir):
  524. non_existent = tmpdir.join("non-existent")
  525. get_adc_path.return_value = str(non_existent)
  526. credentials, project_id = _default._get_gcloud_sdk_credentials()
  527. assert credentials is None
  528. assert project_id is None
  529. @mock.patch(
  530. "google.auth._cloud_sdk.get_project_id",
  531. return_value=mock.sentinel.project_id,
  532. autospec=True,
  533. )
  534. @mock.patch("os.path.isfile", return_value=True, autospec=True)
  535. @LOAD_FILE_PATCH
  536. def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project_id):
  537. # Don't return a project ID from load file, make the function check
  538. # the Cloud SDK project.
  539. load.return_value = MOCK_CREDENTIALS, None
  540. credentials, project_id = _default._get_gcloud_sdk_credentials()
  541. assert credentials == MOCK_CREDENTIALS
  542. assert project_id == mock.sentinel.project_id
  543. assert get_project_id.called
  544. @mock.patch("google.auth._cloud_sdk.get_project_id", return_value=None, autospec=True)
  545. @mock.patch("os.path.isfile", return_value=True)
  546. @LOAD_FILE_PATCH
  547. def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_project_id):
  548. # Don't return a project ID from load file, make the function check
  549. # the Cloud SDK project.
  550. load.return_value = MOCK_CREDENTIALS, None
  551. credentials, project_id = _default._get_gcloud_sdk_credentials()
  552. assert credentials == MOCK_CREDENTIALS
  553. assert project_id is None
  554. assert get_project_id.called
  555. def test__get_gdch_service_account_credentials_invalid_format_version():
  556. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  557. _default._get_gdch_service_account_credentials(
  558. "file_name", {"format_version": "2"}
  559. )
  560. assert excinfo.match("Failed to load GDCH service account credentials")
  561. def test_get_api_key_credentials():
  562. creds = _default.get_api_key_credentials("api_key")
  563. assert isinstance(creds, api_key.Credentials)
  564. assert creds.token == "api_key"
  565. class _AppIdentityModule(object):
  566. """The interface of the App Idenity app engine module.
  567. See https://cloud.google.com/appengine/docs/standard/python/refdocs\
  568. /google.appengine.api.app_identity.app_identity
  569. """
  570. def get_application_id(self):
  571. raise NotImplementedError()
  572. @pytest.fixture
  573. def app_identity(monkeypatch):
  574. """Mocks the app_identity module for google.auth.app_engine."""
  575. app_identity_module = mock.create_autospec(_AppIdentityModule, instance=True)
  576. monkeypatch.setattr(app_engine, "app_identity", app_identity_module)
  577. yield app_identity_module
  578. @mock.patch.dict(os.environ)
  579. def test__get_gae_credentials_gen1(app_identity):
  580. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
  581. app_identity.get_application_id.return_value = mock.sentinel.project
  582. credentials, project_id = _default._get_gae_credentials()
  583. assert isinstance(credentials, app_engine.Credentials)
  584. assert project_id == mock.sentinel.project
  585. @mock.patch.dict(os.environ)
  586. def test__get_gae_credentials_gen2():
  587. os.environ["GAE_RUNTIME"] = "python37"
  588. credentials, project_id = _default._get_gae_credentials()
  589. assert credentials is None
  590. assert project_id is None
  591. @mock.patch.dict(os.environ)
  592. def test__get_gae_credentials_gen2_backwards_compat():
  593. # compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME
  594. # for backwards compatibility with code that relies on it
  595. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37"
  596. os.environ["GAE_RUNTIME"] = "python37"
  597. credentials, project_id = _default._get_gae_credentials()
  598. assert credentials is None
  599. assert project_id is None
  600. def test__get_gae_credentials_env_unset():
  601. assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
  602. assert "GAE_RUNTIME" not in os.environ
  603. credentials, project_id = _default._get_gae_credentials()
  604. assert credentials is None
  605. assert project_id is None
  606. @mock.patch.dict(os.environ)
  607. def test__get_gae_credentials_no_app_engine():
  608. # test both with and without LEGACY_APPENGINE_RUNTIME setting
  609. assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
  610. import sys
  611. with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}):
  612. credentials, project_id = _default._get_gae_credentials()
  613. assert credentials is None
  614. assert project_id is None
  615. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
  616. credentials, project_id = _default._get_gae_credentials()
  617. assert credentials is None
  618. assert project_id is None
  619. @mock.patch.dict(os.environ)
  620. @mock.patch.object(app_engine, "app_identity", new=None)
  621. def test__get_gae_credentials_no_apis():
  622. # test both with and without LEGACY_APPENGINE_RUNTIME setting
  623. assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ
  624. credentials, project_id = _default._get_gae_credentials()
  625. assert credentials is None
  626. assert project_id is None
  627. os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27"
  628. credentials, project_id = _default._get_gae_credentials()
  629. assert credentials is None
  630. assert project_id is None
  631. @mock.patch(
  632. "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True
  633. )
  634. @mock.patch(
  635. "google.auth.compute_engine._metadata.get_project_id",
  636. return_value="example-project",
  637. autospec=True,
  638. )
  639. def test__get_gce_credentials(unused_get, unused_ping):
  640. credentials, project_id = _default._get_gce_credentials()
  641. assert isinstance(credentials, compute_engine.Credentials)
  642. assert project_id == "example-project"
  643. @mock.patch(
  644. "google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True
  645. )
  646. def test__get_gce_credentials_no_ping(unused_ping):
  647. credentials, project_id = _default._get_gce_credentials()
  648. assert credentials is None
  649. assert project_id is None
  650. @mock.patch(
  651. "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True
  652. )
  653. @mock.patch(
  654. "google.auth.compute_engine._metadata.get_project_id",
  655. side_effect=exceptions.TransportError(),
  656. autospec=True,
  657. )
  658. def test__get_gce_credentials_no_project_id(unused_get, unused_ping):
  659. credentials, project_id = _default._get_gce_credentials()
  660. assert isinstance(credentials, compute_engine.Credentials)
  661. assert project_id is None
  662. def test__get_gce_credentials_no_compute_engine():
  663. import sys
  664. with mock.patch.dict("sys.modules"):
  665. sys.modules["google.auth.compute_engine"] = None
  666. credentials, project_id = _default._get_gce_credentials()
  667. assert credentials is None
  668. assert project_id is None
  669. @mock.patch(
  670. "google.auth.compute_engine._metadata.is_on_gce", return_value=False, autospec=True
  671. )
  672. def test__get_gce_credentials_explicit_request(ping):
  673. _default._get_gce_credentials(mock.sentinel.request)
  674. ping.assert_called_with(request=mock.sentinel.request)
  675. @mock.patch(
  676. "google.auth._default._get_explicit_environ_credentials",
  677. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  678. autospec=True,
  679. )
  680. def test_default_early_out(unused_get):
  681. assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id)
  682. @mock.patch(
  683. "google.auth._default._get_explicit_environ_credentials",
  684. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  685. autospec=True,
  686. )
  687. def test_default_explict_project_id(unused_get, monkeypatch):
  688. monkeypatch.setenv(environment_vars.PROJECT, "explicit-env")
  689. assert _default.default() == (MOCK_CREDENTIALS, "explicit-env")
  690. @mock.patch(
  691. "google.auth._default._get_explicit_environ_credentials",
  692. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  693. autospec=True,
  694. )
  695. def test_default_explict_legacy_project_id(unused_get, monkeypatch):
  696. monkeypatch.setenv(environment_vars.LEGACY_PROJECT, "explicit-env")
  697. assert _default.default() == (MOCK_CREDENTIALS, "explicit-env")
  698. @mock.patch("logging.Logger.warning", autospec=True)
  699. @mock.patch(
  700. "google.auth._default._get_explicit_environ_credentials",
  701. return_value=(MOCK_CREDENTIALS, None),
  702. autospec=True,
  703. )
  704. @mock.patch(
  705. "google.auth._default._get_gcloud_sdk_credentials",
  706. return_value=(MOCK_CREDENTIALS, None),
  707. autospec=True,
  708. )
  709. @mock.patch(
  710. "google.auth._default._get_gae_credentials",
  711. return_value=(MOCK_CREDENTIALS, None),
  712. autospec=True,
  713. )
  714. @mock.patch(
  715. "google.auth._default._get_gce_credentials",
  716. return_value=(MOCK_CREDENTIALS, None),
  717. autospec=True,
  718. )
  719. def test_default_without_project_id(
  720. unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning
  721. ):
  722. assert _default.default() == (MOCK_CREDENTIALS, None)
  723. logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY)
  724. @mock.patch(
  725. "google.auth._default._get_explicit_environ_credentials",
  726. return_value=(None, None),
  727. autospec=True,
  728. )
  729. @mock.patch(
  730. "google.auth._default._get_gcloud_sdk_credentials",
  731. return_value=(None, None),
  732. autospec=True,
  733. )
  734. @mock.patch(
  735. "google.auth._default._get_gae_credentials",
  736. return_value=(None, None),
  737. autospec=True,
  738. )
  739. @mock.patch(
  740. "google.auth._default._get_gce_credentials",
  741. return_value=(None, None),
  742. autospec=True,
  743. )
  744. def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit):
  745. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  746. assert _default.default()
  747. assert excinfo.match(_default._CLOUD_SDK_MISSING_CREDENTIALS)
  748. @mock.patch(
  749. "google.auth._default._get_explicit_environ_credentials",
  750. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  751. autospec=True,
  752. )
  753. @mock.patch(
  754. "google.auth.credentials.with_scopes_if_required",
  755. return_value=MOCK_CREDENTIALS,
  756. autospec=True,
  757. )
  758. def test_default_scoped(with_scopes, unused_get):
  759. scopes = ["one", "two"]
  760. credentials, project_id = _default.default(scopes=scopes)
  761. assert credentials == with_scopes.return_value
  762. assert project_id == mock.sentinel.project_id
  763. with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes, default_scopes=None)
  764. @mock.patch(
  765. "google.auth._default._get_explicit_environ_credentials",
  766. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  767. autospec=True,
  768. )
  769. def test_default_quota_project(with_quota_project):
  770. credentials, project_id = _default.default(quota_project_id="project-foo")
  771. MOCK_CREDENTIALS.with_quota_project.assert_called_once_with("project-foo")
  772. assert project_id == mock.sentinel.project_id
  773. @mock.patch(
  774. "google.auth._default._get_explicit_environ_credentials",
  775. return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
  776. autospec=True,
  777. )
  778. def test_default_no_app_engine_compute_engine_module(unused_get):
  779. """
  780. google.auth.compute_engine and google.auth.app_engine are both optional
  781. to allow not including them when using this package. This verifies
  782. that default fails gracefully if these modules are absent
  783. """
  784. import sys
  785. with mock.patch.dict("sys.modules"):
  786. sys.modules["google.auth.compute_engine"] = None
  787. sys.modules["google.auth.app_engine"] = None
  788. assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id)
  789. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  790. def test_default_environ_external_credentials_identity_pool(
  791. get_project_id, monkeypatch, tmpdir
  792. ):
  793. config_file = tmpdir.join("config.json")
  794. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  795. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  796. credentials, project_id = _default.default()
  797. assert isinstance(credentials, identity_pool.Credentials)
  798. assert not credentials.is_user
  799. assert not credentials.is_workforce_pool
  800. # Without scopes, project ID cannot be determined.
  801. assert project_id is None
  802. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  803. def test_default_environ_external_credentials_identity_pool_impersonated(
  804. get_project_id, monkeypatch, tmpdir
  805. ):
  806. config_file = tmpdir.join("config.json")
  807. config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA))
  808. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  809. credentials, project_id = _default.default(
  810. scopes=["https://www.google.com/calendar/feeds"]
  811. )
  812. assert isinstance(credentials, identity_pool.Credentials)
  813. assert not credentials.is_user
  814. assert not credentials.is_workforce_pool
  815. assert project_id is mock.sentinel.project_id
  816. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  817. # The credential.get_project_id should have been used in _get_external_account_credentials and default
  818. assert get_project_id.call_count == 2
  819. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  820. @mock.patch.dict(os.environ)
  821. def test_default_environ_external_credentials_project_from_env(
  822. get_project_id, monkeypatch, tmpdir
  823. ):
  824. project_from_env = "project_from_env"
  825. os.environ[environment_vars.PROJECT] = project_from_env
  826. config_file = tmpdir.join("config.json")
  827. config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA))
  828. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  829. credentials, project_id = _default.default(
  830. scopes=["https://www.google.com/calendar/feeds"]
  831. )
  832. assert isinstance(credentials, identity_pool.Credentials)
  833. assert not credentials.is_user
  834. assert not credentials.is_workforce_pool
  835. assert project_id == project_from_env
  836. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  837. # The credential.get_project_id should have been used only in _get_external_account_credentials
  838. assert get_project_id.call_count == 1
  839. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  840. @mock.patch.dict(os.environ)
  841. def test_default_environ_external_credentials_legacy_project_from_env(
  842. get_project_id, monkeypatch, tmpdir
  843. ):
  844. project_from_env = "project_from_env"
  845. os.environ[environment_vars.LEGACY_PROJECT] = project_from_env
  846. config_file = tmpdir.join("config.json")
  847. config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA))
  848. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  849. credentials, project_id = _default.default(
  850. scopes=["https://www.google.com/calendar/feeds"]
  851. )
  852. assert isinstance(credentials, identity_pool.Credentials)
  853. assert not credentials.is_user
  854. assert not credentials.is_workforce_pool
  855. assert project_id == project_from_env
  856. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  857. # The credential.get_project_id should have been used only in _get_external_account_credentials
  858. assert get_project_id.call_count == 1
  859. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  860. def test_default_environ_external_credentials_aws_impersonated(
  861. get_project_id, monkeypatch, tmpdir
  862. ):
  863. config_file = tmpdir.join("config.json")
  864. config_file.write(json.dumps(IMPERSONATED_AWS_DATA))
  865. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  866. credentials, project_id = _default.default(
  867. scopes=["https://www.google.com/calendar/feeds"]
  868. )
  869. assert isinstance(credentials, aws.Credentials)
  870. assert not credentials.is_user
  871. assert not credentials.is_workforce_pool
  872. assert project_id is mock.sentinel.project_id
  873. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  874. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  875. def test_default_environ_external_credentials_workforce(
  876. get_project_id, monkeypatch, tmpdir
  877. ):
  878. config_file = tmpdir.join("config.json")
  879. config_file.write(json.dumps(IDENTITY_POOL_WORKFORCE_DATA))
  880. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  881. credentials, project_id = _default.default(
  882. scopes=["https://www.google.com/calendar/feeds"]
  883. )
  884. assert isinstance(credentials, identity_pool.Credentials)
  885. assert credentials.is_user
  886. assert credentials.is_workforce_pool
  887. assert project_id is mock.sentinel.project_id
  888. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  889. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  890. def test_default_environ_external_credentials_workforce_impersonated(
  891. get_project_id, monkeypatch, tmpdir
  892. ):
  893. config_file = tmpdir.join("config.json")
  894. config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA))
  895. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  896. credentials, project_id = _default.default(
  897. scopes=["https://www.google.com/calendar/feeds"]
  898. )
  899. assert isinstance(credentials, identity_pool.Credentials)
  900. assert not credentials.is_user
  901. assert credentials.is_workforce_pool
  902. assert project_id is mock.sentinel.project_id
  903. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  904. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  905. def test_default_environ_external_credentials_with_user_and_default_scopes_and_quota_project_id(
  906. get_project_id, monkeypatch, tmpdir
  907. ):
  908. config_file = tmpdir.join("config.json")
  909. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  910. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  911. credentials, project_id = _default.default(
  912. scopes=["https://www.google.com/calendar/feeds"],
  913. default_scopes=["https://www.googleapis.com/auth/cloud-platform"],
  914. quota_project_id="project-foo",
  915. )
  916. assert isinstance(credentials, identity_pool.Credentials)
  917. assert project_id is mock.sentinel.project_id
  918. assert credentials.quota_project_id == "project-foo"
  919. assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
  920. assert credentials.default_scopes == [
  921. "https://www.googleapis.com/auth/cloud-platform"
  922. ]
  923. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  924. def test_default_environ_external_credentials_explicit_request_with_scopes(
  925. get_project_id, monkeypatch, tmpdir
  926. ):
  927. config_file = tmpdir.join("config.json")
  928. config_file.write(json.dumps(IDENTITY_POOL_DATA))
  929. monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file))
  930. credentials, project_id = _default.default(
  931. request=mock.sentinel.request,
  932. scopes=["https://www.googleapis.com/auth/cloud-platform"],
  933. )
  934. assert isinstance(credentials, identity_pool.Credentials)
  935. assert project_id is mock.sentinel.project_id
  936. # default() will initialize new credentials via with_scopes_if_required
  937. # and potentially with_quota_project.
  938. # As a result the caller of get_project_id() will not match the returned
  939. # credentials.
  940. get_project_id.assert_called_with(mock.ANY, request=mock.sentinel.request)
  941. def test_default_environ_external_credentials_bad_format(monkeypatch, tmpdir):
  942. filename = tmpdir.join("external_account_bad.json")
  943. filename.write(json.dumps({"type": "external_account"}))
  944. monkeypatch.setenv(environment_vars.CREDENTIALS, str(filename))
  945. with pytest.raises(exceptions.DefaultCredentialsError) as excinfo:
  946. _default.default()
  947. assert excinfo.match(
  948. "Failed to load external account credentials from {}".format(str(filename))
  949. )
  950. @mock.patch(
  951. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  952. )
  953. def test_default_warning_without_quota_project_id_for_user_creds(get_adc_path):
  954. get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE
  955. with pytest.warns(UserWarning, match=_default._CLOUD_SDK_CREDENTIALS_WARNING):
  956. credentials, project_id = _default.default(quota_project_id=None)
  957. @mock.patch(
  958. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  959. )
  960. def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path):
  961. get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE
  962. credentials, project_id = _default.default(quota_project_id="project-foo")
  963. @mock.patch(
  964. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  965. )
  966. def test_default_impersonated_service_account(get_adc_path):
  967. get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE
  968. credentials, _ = _default.default()
  969. assert isinstance(credentials, impersonated_credentials.Credentials)
  970. assert isinstance(
  971. credentials._source_credentials, google.oauth2.credentials.Credentials
  972. )
  973. assert credentials.service_account_email == "service-account-target@example.com"
  974. assert credentials._delegates == ["service-account-delegate@example.com"]
  975. assert not credentials._quota_project_id
  976. assert not credentials._target_scopes
  977. @mock.patch(
  978. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  979. )
  980. def test_default_impersonated_service_account_set_scopes(get_adc_path):
  981. get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE
  982. scopes = ["scope1", "scope2"]
  983. credentials, _ = _default.default(scopes=scopes)
  984. assert credentials._target_scopes == scopes
  985. @mock.patch(
  986. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  987. )
  988. def test_default_impersonated_service_account_set_default_scopes(get_adc_path):
  989. get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE
  990. default_scopes = ["scope1", "scope2"]
  991. credentials, _ = _default.default(default_scopes=default_scopes)
  992. assert credentials._target_scopes == default_scopes
  993. @mock.patch(
  994. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  995. )
  996. def test_default_impersonated_service_account_set_both_scopes_and_default_scopes(
  997. get_adc_path
  998. ):
  999. get_adc_path.return_value = IMPERSONATED_SERVICE_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE
  1000. scopes = ["scope1", "scope2"]
  1001. default_scopes = ["scope3", "scope4"]
  1002. credentials, _ = _default.default(scopes=scopes, default_scopes=default_scopes)
  1003. assert credentials._target_scopes == scopes
  1004. @EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH
  1005. def test_load_credentials_from_external_account_pluggable(get_project_id, tmpdir):
  1006. config_file = tmpdir.join("config.json")
  1007. config_file.write(json.dumps(PLUGGABLE_DATA))
  1008. credentials, project_id = _default.load_credentials_from_file(str(config_file))
  1009. assert isinstance(credentials, pluggable.Credentials)
  1010. # Since no scopes are specified, the project ID cannot be determined.
  1011. assert project_id is None
  1012. assert get_project_id.called
  1013. @mock.patch(
  1014. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  1015. )
  1016. def test_default_gdch_service_account_credentials(get_adc_path):
  1017. get_adc_path.return_value = GDCH_SERVICE_ACCOUNT_FILE
  1018. creds, project = _default.default(quota_project_id="project-foo")
  1019. assert isinstance(creds, gdch_credentials.ServiceAccountCredentials)
  1020. assert creds._service_identity_name == "service_identity_name"
  1021. assert creds._audience is None
  1022. assert creds._token_uri == "https://service-identity.<Domain>/authenticate"
  1023. assert creds._ca_cert_path == "/path/to/ca/cert"
  1024. assert project == "project_foo"
  1025. @mock.patch.dict(os.environ)
  1026. @mock.patch(
  1027. "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True
  1028. )
  1029. def test_quota_project_from_environment(get_adc_path):
  1030. get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE
  1031. credentials, _ = _default.default(quota_project_id=None)
  1032. assert credentials.quota_project_id == "quota_project_id"
  1033. quota_from_env = "quota_from_env"
  1034. os.environ[environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT] = quota_from_env
  1035. credentials, _ = _default.default(quota_project_id=None)
  1036. assert credentials.quota_project_id == quota_from_env
  1037. explicit_quota = "explicit_quota"
  1038. credentials, _ = _default.default(quota_project_id=explicit_quota)
  1039. assert credentials.quota_project_id == explicit_quota
  1040. @mock.patch(
  1041. "google.auth.compute_engine._metadata.is_on_gce", return_value=True, autospec=True
  1042. )
  1043. @mock.patch(
  1044. "google.auth.compute_engine._metadata.get_project_id",
  1045. return_value="example-project",
  1046. autospec=True,
  1047. )
  1048. @mock.patch.dict(os.environ)
  1049. def test_quota_gce_credentials(unused_get, unused_ping):
  1050. # No quota
  1051. credentials, project_id = _default._get_gce_credentials()
  1052. assert project_id == "example-project"
  1053. assert credentials.quota_project_id is None
  1054. # Quota from environment
  1055. quota_from_env = "quota_from_env"
  1056. os.environ[environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT] = quota_from_env
  1057. credentials, project_id = _default._get_gce_credentials()
  1058. assert credentials.quota_project_id == quota_from_env
  1059. # Explicit quota
  1060. explicit_quota = "explicit_quota"
  1061. credentials, project_id = _default._get_gce_credentials(
  1062. quota_project_id=explicit_quota
  1063. )
  1064. assert credentials.quota_project_id == explicit_quota