test__default.py 51 KB

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