diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-11-21 11:14:32 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-11-21 11:23:42 +0300 |
commit | ed5b11df0064bcf6ef0d03aa0f1a34f4aa97851d (patch) | |
tree | 89842a7525bcc7f2809cbbc4360f6394d0ef929b | |
parent | 532123792431edd487519a254f2248603c2056e7 (diff) | |
download | ydb-ed5b11df0064bcf6ef0d03aa0f1a34f4aa97851d.tar.gz |
Intermediate changes
commit_hash:9085ddac9f80e60b5b938027d444ed98e80ef95a
19 files changed, 311 insertions, 60 deletions
diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA index 261e2a0276..500fd7bdbc 100644 --- a/contrib/python/google-auth/py3/.dist-info/METADATA +++ b/contrib/python/google-auth/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-auth -Version: 2.35.0 +Version: 2.36.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform @@ -24,22 +24,22 @@ Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Requires-Python: >=3.7 License-File: LICENSE -Requires-Dist: cachetools <6.0,>=2.0.0 -Requires-Dist: pyasn1-modules >=0.2.1 -Requires-Dist: rsa <5,>=3.1.4 +Requires-Dist: cachetools<6.0,>=2.0.0 +Requires-Dist: pyasn1-modules>=0.2.1 +Requires-Dist: rsa<5,>=3.1.4 Provides-Extra: aiohttp -Requires-Dist: aiohttp <4.0.0.dev0,>=3.6.2 ; extra == 'aiohttp' -Requires-Dist: requests <3.0.0.dev0,>=2.20.0 ; extra == 'aiohttp' +Requires-Dist: aiohttp<4.0.0.dev0,>=3.6.2; extra == "aiohttp" +Requires-Dist: requests<3.0.0.dev0,>=2.20.0; extra == "aiohttp" Provides-Extra: enterprise_cert -Requires-Dist: cryptography ; extra == 'enterprise_cert' -Requires-Dist: pyopenssl ; extra == 'enterprise_cert' +Requires-Dist: cryptography; extra == "enterprise-cert" +Requires-Dist: pyopenssl; extra == "enterprise-cert" Provides-Extra: pyopenssl -Requires-Dist: pyopenssl >=20.0.0 ; extra == 'pyopenssl' -Requires-Dist: cryptography >=38.0.3 ; extra == 'pyopenssl' +Requires-Dist: pyopenssl>=20.0.0; extra == "pyopenssl" +Requires-Dist: cryptography>=38.0.3; extra == "pyopenssl" Provides-Extra: reauth -Requires-Dist: pyu2f >=0.1.5 ; extra == 'reauth' +Requires-Dist: pyu2f>=0.1.5; extra == "reauth" Provides-Extra: requests -Requires-Dist: requests <3.0.0.dev0,>=2.20.0 ; extra == 'requests' +Requires-Dist: requests<3.0.0.dev0,>=2.20.0; extra == "requests" Google Auth Python Library ========================== diff --git a/contrib/python/google-auth/py3/google/auth/_default.py b/contrib/python/google-auth/py3/google/auth/_default.py index 7bbcf85914..cdc8b7a646 100644 --- a/contrib/python/google-auth/py3/google/auth/_default.py +++ b/contrib/python/google-auth/py3/google/auth/_default.py @@ -472,6 +472,10 @@ def _get_impersonated_service_account_credentials(filename, info, scopes): source_credentials, _ = _get_service_account_credentials( filename, source_credentials_info ) + elif source_credentials_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: + source_credentials, _ = _get_external_account_authorized_user_credentials( + filename, source_credentials_info + ) else: raise exceptions.InvalidType( "source credential of type {} is not supported.".format( diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py index b66d9f9b37..8d692972fd 100644 --- a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py +++ b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py @@ -294,7 +294,7 @@ def get_universe_domain(request): 404 occurs while retrieving metadata. """ universe_domain = get( - request, "universe/universe_domain", return_none_for_not_found_error=True + request, "universe/universe-domain", return_none_for_not_found_error=True ) if not universe_domain: return "googleapis.com" diff --git a/contrib/python/google-auth/py3/google/auth/iam.py b/contrib/python/google-auth/py3/google/auth/iam.py index bba1624c16..dcf0dbf9d5 100644 --- a/contrib/python/google-auth/py3/google/auth/iam.py +++ b/contrib/python/google-auth/py3/google/auth/iam.py @@ -23,10 +23,18 @@ import base64 import http.client as http_client import json +from google.auth import _exponential_backoff from google.auth import _helpers +from google.auth import credentials from google.auth import crypt from google.auth import exceptions +IAM_RETRY_CODES = { + http_client.INTERNAL_SERVER_ERROR, + http_client.BAD_GATEWAY, + http_client.SERVICE_UNAVAILABLE, + http_client.GATEWAY_TIMEOUT, +} _IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] @@ -82,21 +90,30 @@ class Signer(crypt.Signer): message = _helpers.to_bytes(message) method = "POST" - url = _IAM_SIGN_ENDPOINT.format(self._service_account_email) + url = _IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self._credentials.universe_domain + ).format(self._service_account_email) headers = {"Content-Type": "application/json"} body = json.dumps( {"payload": base64.b64encode(message).decode("utf-8")} ).encode("utf-8") - self._credentials.before_request(self._request, method, url, headers) - response = self._request(url=url, method=method, body=body, headers=headers) + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + self._credentials.before_request(self._request, method, url, headers) - if response.status != http_client.OK: - raise exceptions.TransportError( - "Error calling the IAM signBlob API: {}".format(response.data) - ) + response = self._request(url=url, method=method, body=body, headers=headers) - return json.loads(response.data.decode("utf-8")) + if response.status in IAM_RETRY_CODES: + continue + + if response.status != http_client.OK: + raise exceptions.TransportError( + "Error calling the IAM signBlob API: {}".format(response.data) + ) + + return json.loads(response.data.decode("utf-8")) + raise exceptions.TransportError("exhausted signBlob endpoint retries") @property def key_id(self): diff --git a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py index c42a936433..d51c8ef1e8 100644 --- a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py +++ b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py @@ -31,6 +31,7 @@ from datetime import datetime import http.client as http_client import json +from google.auth import _exponential_backoff from google.auth import _helpers from google.auth import credentials from google.auth import exceptions @@ -45,7 +46,12 @@ _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds def _make_iam_token_request( - request, principal, headers, body, iam_endpoint_override=None + request, + principal, + headers, + body, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + iam_endpoint_override=None, ): """Makes a request to the Google Cloud IAM service for an access token. Args: @@ -66,7 +72,9 @@ def _make_iam_token_request( `iamcredentials.googleapis.com` is not enabled or the `Service Account Token Creator` is not assigned """ - iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.format(principal) + iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(principal) body = json.dumps(body).encode("utf-8") @@ -218,6 +226,8 @@ class Credentials( and self._source_credentials._always_use_jwt_access ): self._source_credentials._create_self_signed_jwt(None) + + self._universe_domain = source_credentials.universe_domain self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates @@ -270,13 +280,16 @@ class Credentials( principal=self._target_principal, headers=headers, body=body, + universe_domain=self.universe_domain, iam_endpoint_override=self._iam_endpoint_override, ) def sign_bytes(self, message): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.format(self._target_principal) + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain + ).format(self._target_principal) body = { "payload": base64.b64encode(message).decode("utf-8"), @@ -288,18 +301,22 @@ class Credentials( authed_session = AuthorizedSession(self._source_credentials) try: - response = authed_session.post( - url=iam_sign_endpoint, headers=headers, json=body - ) + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + response = authed_session.post( + url=iam_sign_endpoint, headers=headers, json=body + ) + if response.status_code in iam.IAM_RETRY_CODES: + continue + if response.status_code != http_client.OK: + raise exceptions.TransportError( + "Error calling sign_bytes: {}".format(response.json()) + ) + + return base64.b64decode(response.json()["signedBlob"]) finally: authed_session.close() - - if response.status_code != http_client.OK: - raise exceptions.TransportError( - "Error calling sign_bytes: {}".format(response.json()) - ) - - return base64.b64decode(response.json()["signedBlob"]) + raise exceptions.TransportError("exhausted signBlob endpoint retries") @property def signer_email(self): @@ -422,9 +439,10 @@ class IDTokenCredentials(credentials.CredentialsWithQuotaProject): def refresh(self, request): from google.auth.transport.requests import AuthorizedSession - iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.format( - self._target_credentials.signer_email - ) + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, + self._target_credentials.universe_domain, + ).format(self._target_credentials.signer_email) body = { "audience": self._target_audience, diff --git a/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py b/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py index ec718d909a..0608223d8c 100644 --- a/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py +++ b/contrib/python/google-auth/py3/google/auth/transport/_requests_base.py @@ -13,7 +13,8 @@ # limitations under the License. """Transport adapter for Base Requests.""" - +# NOTE: The coverage for this file is temporarily disabled in `.coveragerc` +# since it is currently unused. import abc diff --git a/contrib/python/google-auth/py3/google/auth/transport/requests.py b/contrib/python/google-auth/py3/google/auth/transport/requests.py index 68f67c59bd..23a69783dc 100644 --- a/contrib/python/google-auth/py3/google/auth/transport/requests.py +++ b/contrib/python/google-auth/py3/google/auth/transport/requests.py @@ -38,7 +38,6 @@ from google.auth import environment_vars from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper -from google.auth.transport._requests_base import _BaseAuthorizedSession from google.oauth2 import service_account _LOGGER = logging.getLogger(__name__) @@ -293,7 +292,7 @@ class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter): return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) -class AuthorizedSession(requests.Session, _BaseAuthorizedSession): +class AuthorizedSession(requests.Session): """A Requests Session class with credentials. This class is used to perform requests to API endpoints that require @@ -390,7 +389,7 @@ class AuthorizedSession(requests.Session, _BaseAuthorizedSession): default_host=None, ): super(AuthorizedSession, self).__init__() - _BaseAuthorizedSession.__init__(self, credentials) + self.credentials = credentials self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py index 6610120c69..15dc374707 100644 --- a/contrib/python/google-auth/py3/google/auth/version.py +++ b/contrib/python/google-auth/py3/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.35.0" +__version__ = "2.36.0" diff --git a/contrib/python/google-auth/py3/google/oauth2/_client.py b/contrib/python/google-auth/py3/google/oauth2/_client.py index 68e13ddc73..5a9fc3503c 100644 --- a/contrib/python/google-auth/py3/google/oauth2/_client.py +++ b/contrib/python/google-auth/py3/google/oauth2/_client.py @@ -30,6 +30,7 @@ import urllib from google.auth import _exponential_backoff from google.auth import _helpers +from google.auth import credentials from google.auth import exceptions from google.auth import jwt from google.auth import metrics @@ -319,7 +320,12 @@ def jwt_grant(request, token_uri, assertion, can_retry=True): def call_iam_generate_id_token_endpoint( - request, iam_id_token_endpoint, signer_email, audience, access_token + request, + iam_id_token_endpoint, + signer_email, + audience, + access_token, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, ): """Call iam.generateIdToken endpoint to get ID token. @@ -339,7 +345,9 @@ def call_iam_generate_id_token_endpoint( response_data = _token_endpoint_request( request, - iam_id_token_endpoint.format(signer_email), + iam_id_token_endpoint.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(signer_email), body, access_token=access_token, use_json=True, diff --git a/contrib/python/google-auth/py3/google/oauth2/service_account.py b/contrib/python/google-auth/py3/google/oauth2/service_account.py index 98dafa3e38..3e84194ac7 100644 --- a/contrib/python/google-auth/py3/google/oauth2/service_account.py +++ b/contrib/python/google-auth/py3/google/oauth2/service_account.py @@ -812,6 +812,7 @@ class IDTokenCredentials( self.signer_email, self._target_audience, jwt_credentials.token.decode(), + self._universe_domain, ) @_helpers.copy_docstring(credentials.Credentials) diff --git a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py index a06dc4fa19..03ba8de497 100644 --- a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py +++ b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py @@ -399,7 +399,7 @@ def test_get_universe_domain_success(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert universe_domain == "fake_universe_domain" @@ -412,7 +412,7 @@ def test_get_universe_domain_success_empty_response(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert universe_domain == "googleapis.com" @@ -427,7 +427,7 @@ def test_get_universe_domain_not_found(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert universe_domain == "googleapis.com" @@ -447,7 +447,7 @@ def test_get_universe_domain_retryable_error_failure(): request.assert_called_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) assert request.call_count == 5 @@ -489,12 +489,12 @@ def test_get_universe_domain_retryable_error_success(): request_error.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) request_ok.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) @@ -513,7 +513,7 @@ def test_get_universe_domain_other_error(): request.assert_called_once_with( method="GET", - url=_metadata._METADATA_ROOT + "universe/universe_domain", + url=_metadata._METADATA_ROOT + "universe/universe-domain", headers=_metadata._METADATA_HEADERS, ) diff --git a/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py b/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py index 662210fa41..fddfb7f64d 100644 --- a/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py +++ b/contrib/python/google-auth/py3/tests/compute_engine/test_credentials.py @@ -487,6 +487,16 @@ class TestIDTokenCredentials(object): }, ) + # mock information about universe_domain + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/universe/" + "universe-domain", + status=200, + content_type="application/json", + json={}, + ) + # mock token for credentials responses.add( responses.GET, @@ -659,6 +669,16 @@ class TestIDTokenCredentials(object): }, ) + # stubby response about universe_domain + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/universe/" + "universe-domain", + status=200, + content_type="application/json", + json={}, + ) + # mock sign blob endpoint signature = base64.b64encode(b"some-signature").decode("utf-8") responses.add( diff --git a/contrib/python/google-auth/py3/tests/data/impersonated_service_account_external_account_authorized_user_source.json b/contrib/python/google-auth/py3/tests/data/impersonated_service_account_external_account_authorized_user_source.json new file mode 100644 index 0000000000..0bc44c13a2 --- /dev/null +++ b/contrib/python/google-auth/py3/tests/data/impersonated_service_account_external_account_authorized_user_source.json @@ -0,0 +1,16 @@ +{ + "delegates": [ + "service-account-delegate@example.com" + ], + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/service-account-target@example.com:generateAccessToken", + "source_credentials": { + "type": "external_account_authorized_user", + "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID", + "refresh_token": "refreshToken", + "token_url": "https://sts.googleapis.com/v1/oauth/token", + "token_info_url": "https://sts.googleapis.com/v1/instrospect", + "client_id": "clientId", + "client_secret": "clientSecret" + }, + "type": "impersonated_service_account" +}
\ No newline at end of file diff --git a/contrib/python/google-auth/py3/tests/oauth2/test__client.py b/contrib/python/google-auth/py3/tests/oauth2/test__client.py index 8736a4e27b..df77bbcd2f 100644 --- a/contrib/python/google-auth/py3/tests/oauth2/test__client.py +++ b/contrib/python/google-auth/py3/tests/oauth2/test__client.py @@ -325,6 +325,7 @@ def test_call_iam_generate_id_token_endpoint(): "fake_email", "fake_audience", "fake_access_token", + "googleapis.com", ) assert ( @@ -362,6 +363,7 @@ def test_call_iam_generate_id_token_endpoint_no_id_token(): "fake_email", "fake_audience", "fake_access_token", + "googleapis.com", ) assert excinfo.match("No ID token in response") diff --git a/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py b/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py index fe02e828e7..e60c8200f4 100644 --- a/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py +++ b/contrib/python/google-auth/py3/tests/oauth2/test_service_account.py @@ -790,7 +790,7 @@ class TestIDTokenCredentials(object): ) request = mock.Mock() credentials.refresh(request) - req, iam_endpoint, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + req, iam_endpoint, signer_email, target_audience, access_token, universe_domain = call_iam_generate_id_token_endpoint.call_args[ 0 ] assert req == request @@ -812,7 +812,7 @@ class TestIDTokenCredentials(object): ) request = mock.Mock() credentials.refresh(request) - req, iam_endpoint, signer_email, target_audience, access_token = call_iam_generate_id_token_endpoint.call_args[ + req, iam_endpoint, signer_email, target_audience, access_token, universe_domain = call_iam_generate_id_token_endpoint.call_args[ 0 ] assert req == request diff --git a/contrib/python/google-auth/py3/tests/test__default.py b/contrib/python/google-auth/py3/tests/test__default.py index 3147d505da..f71594fcc5 100644 --- a/contrib/python/google-auth/py3/tests/test__default.py +++ b/contrib/python/google-auth/py3/tests/test__default.py @@ -154,6 +154,11 @@ IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE = os.path.join( DATA_DIR, "impersonated_service_account_service_account_source.json" ) +IMPERSONATED_SERVICE_ACCOUNT_EXTERNAL_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE = os.path.join( + DATA_DIR, + "impersonated_service_account_external_account_authorized_user_source.json", +) + EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE = os.path.join( DATA_DIR, "external_account_authorized_user.json" ) @@ -366,6 +371,17 @@ def test_load_credentials_from_file_impersonated_with_service_account_source(): assert not credentials._quota_project_id +def test_load_credentials_from_file_impersonated_with_external_account_authorized_user_source(): + credentials, _ = _default.load_credentials_from_file( + IMPERSONATED_SERVICE_ACCOUNT_EXTERNAL_ACCOUNT_AUTHORIZED_USER_SOURCE_FILE + ) + assert isinstance(credentials, impersonated_credentials.Credentials) + assert isinstance( + credentials._source_credentials, external_account_authorized_user.Credentials + ) + assert not credentials._quota_project_id + + def test_load_credentials_from_file_impersonated_passing_quota_project(): credentials, _ = _default.load_credentials_from_file( IMPERSONATED_SERVICE_ACCOUNT_SERVICE_ACCOUNT_SOURCE_FILE, diff --git a/contrib/python/google-auth/py3/tests/test_iam.py b/contrib/python/google-auth/py3/tests/test_iam.py index 6706afb4b5..01c2fa085a 100644 --- a/contrib/python/google-auth/py3/tests/test_iam.py +++ b/contrib/python/google-auth/py3/tests/test_iam.py @@ -91,6 +91,7 @@ class TestSigner(object): assert returned_signature == signature kwargs = request.call_args[1] assert kwargs["headers"]["Content-Type"] == "application/json" + request.call_count == 1 def test_sign_bytes_failure(self): request = make_request(http_client.UNAUTHORIZED) @@ -100,3 +101,15 @@ class TestSigner(object): with pytest.raises(exceptions.TransportError): signer.sign("123") + request.call_count == 1 + + @mock.patch("time.sleep", return_value=None) + def test_sign_bytes_retryable_failure(self, mock_time): + request = make_request(http_client.INTERNAL_SERVER_ERROR) + credentials = make_credentials() + + signer = iam.Signer(request, credentials, mock.sentinel.service_account_email) + + with pytest.raises(exceptions.TransportError): + signer.sign("123") + request.call_count == 3 diff --git a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py index 4fb68103a8..371477b8a9 100644 --- a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py +++ b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py @@ -147,6 +147,13 @@ class TestImpersonatedCredentials(object): "principal": "impersonated@project.iam.gserviceaccount.com", } + def test_universe_domain_matching_source(self): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials(source_credentials=source_credentials) + assert credentials.universe_domain == "foo.bar" + def test__make_copy_get_cred_info(self): credentials = self.make_credentials() credentials._cred_file_path = "/path/to/file" @@ -233,6 +240,38 @@ class TestImpersonatedCredentials(object): ) @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_success_nonGdu(self, use_data_bytes, mock_donor_credentials): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + token = "token" + + expire_time = ( + _helpers.utcnow().replace(microsecond=0) + datetime.timedelta(seconds=500) + ).isoformat("T") + "Z" + response_body = {"accessToken": token, "expireTime": expire_time} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + # Confirm override endpoint used. + request_kwargs = request.call_args[1] + assert ( + request_kwargs["url"] + == "https://iamcredentials.foo.bar/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:generateAccessToken" + ) + + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_success_iam_endpoint_override( self, use_data_bytes, mock_donor_credentials ): @@ -398,6 +437,38 @@ class TestImpersonatedCredentials(object): def test_sign_bytes(self, mock_donor_credentials, mock_authorizedsession_sign): credentials = self.make_credentials(lifetime=None) + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:signBlob" + self._sign_bytes_helper( + credentials, + mock_donor_credentials, + mock_authorizedsession_sign, + expected_url, + ) + + def test_sign_bytes_nonGdu( + self, mock_donor_credentials, mock_authorizedsession_sign + ): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + expected_url = "https://iamcredentials.foo.bar/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:signBlob" + self._sign_bytes_helper( + credentials, + mock_donor_credentials, + mock_authorizedsession_sign, + expected_url, + ) + + def _sign_bytes_helper( + self, + credentials, + mock_donor_credentials, + mock_authorizedsession_sign, + expected_url, + ): token = "token" expire_time = ( @@ -413,11 +484,19 @@ class TestImpersonatedCredentials(object): request.return_value = response credentials.refresh(request) - assert credentials.valid assert not credentials.expired signature = credentials.sign_bytes(b"signed bytes") + mock_authorizedsession_sign.assert_called_with( + mock.ANY, + "POST", + expected_url, + None, + json={"payload": "c2lnbmVkIGJ5dGVz", "delegates": []}, + headers={"Content-Type": "application/json"}, + ) + assert signature == b"signature" def test_sign_bytes_failure(self): @@ -427,12 +506,28 @@ class TestImpersonatedCredentials(object): "google.auth.transport.requests.AuthorizedSession.request", autospec=True ) as auth_session: data = {"error": {"code": 403, "message": "unauthorized"}} - auth_session.return_value = MockResponse(data, http_client.FORBIDDEN) + mock_response = MockResponse(data, http_client.UNAUTHORIZED) + auth_session.return_value = mock_response with pytest.raises(exceptions.TransportError) as excinfo: credentials.sign_bytes(b"foo") assert excinfo.match("'code': 403") + @mock.patch("time.sleep", return_value=None) + def test_sign_bytes_retryable_failure(self, mock_time): + credentials = self.make_credentials(lifetime=None) + + with mock.patch( + "google.auth.transport.requests.AuthorizedSession.request", autospec=True + ) as auth_session: + data = {"error": {"code": 500, "message": "internal_failure"}} + mock_response = MockResponse(data, http_client.INTERNAL_SERVER_ERROR) + auth_session.return_value = mock_response + + with pytest.raises(exceptions.TransportError) as excinfo: + credentials.sign_bytes(b"foo") + assert excinfo.match("exhausted signBlob endpoint retries") + def test_with_quota_project(self): credentials = self.make_credentials() @@ -548,6 +643,45 @@ class TestImpersonatedCredentials(object): self, mock_donor_credentials, mock_authorizedsession_idtoken ): credentials = self.make_credentials(lifetime=None) + target_credentials = self.make_credentials(lifetime=None) + expected_url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:generateIdToken" + self._test_id_token_helper( + credentials, + target_credentials, + mock_donor_credentials, + mock_authorizedsession_idtoken, + expected_url, + ) + + def test_id_token_from_credential_nonGdu( + self, mock_donor_credentials, mock_authorizedsession_idtoken + ): + source_credentials = service_account.Credentials( + SIGNER, "some@email.com", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + target_credentials = self.make_credentials( + lifetime=None, source_credentials=source_credentials + ) + expected_url = "https://iamcredentials.foo.bar/v1/projects/-/serviceAccounts/impersonated@project.iam.gserviceaccount.com:generateIdToken" + self._test_id_token_helper( + credentials, + target_credentials, + mock_donor_credentials, + mock_authorizedsession_idtoken, + expected_url, + ) + + def _test_id_token_helper( + self, + credentials, + target_credentials, + mock_donor_credentials, + mock_authorizedsession_idtoken, + expected_url, + ): token = "token" target_audience = "https://foo.bar" @@ -565,17 +699,19 @@ class TestImpersonatedCredentials(object): assert credentials.valid assert not credentials.expired - new_credentials = self.make_credentials(lifetime=None) - id_creds = impersonated_credentials.IDTokenCredentials( credentials, target_audience=target_audience, include_email=True ) - id_creds = id_creds.from_credentials(target_credentials=new_credentials) + id_creds = id_creds.from_credentials(target_credentials=target_credentials) id_creds.refresh(request) + args = mock_authorizedsession_idtoken.call_args.args + + assert args[2] == expected_url + assert id_creds.token == ID_TOKEN_DATA assert id_creds._include_email is True - assert id_creds._target_credentials is new_credentials + assert id_creds._target_credentials is target_credentials def test_id_token_with_target_audience( self, mock_donor_credentials, mock_authorizedsession_idtoken diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make index 60146f91a4..a518b37365 100644 --- a/contrib/python/google-auth/py3/ya.make +++ b/contrib/python/google-auth/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.35.0) +VERSION(2.36.0) LICENSE(Apache-2.0) |