diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a419d0b44..f822ba74a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [2.188.0](https://github.com/googleapis/google-api-python-client/compare/v2.187.0...v2.188.0) (2025-12-05) + + +### Features + +* Auto-enable mTLS when supported certificates are detected ([#2686](https://github.com/googleapis/google-api-python-client/issues/2686)) ([a1fd6f3](https://github.com/googleapis/google-api-python-client/commit/a1fd6f358f475a17612fe374502666581d571a8c)) + ## [2.187.0](https://github.com/googleapis/google-api-python-client/compare/v2.186.0...v2.187.0) (2025-11-04) diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index 62d243bc29..e1f7ee48df 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -660,7 +660,7 @@ def build_from_document( if use_client_cert_str not in ("true", "false"): raise MutualTLSChannelError( "Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: true, false" - ) + ) if client_options and client_options.client_cert_source: raise MutualTLSChannelError( "ClientOptions.client_cert_source is not supported, please use ClientOptions.client_encrypted_cert_source." diff --git a/googleapiclient/version.py b/googleapiclient/version.py index 55f64019ea..aff5f84f98 100644 --- a/googleapiclient/version.py +++ b/googleapiclient/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.187.0" +__version__ = "2.188.0" diff --git a/tests/test_discovery.py b/tests/test_discovery.py index f60f84c276..2d74ce6dfc 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -32,7 +32,6 @@ import json import os import pickle -import pytest import re import sys import unittest @@ -40,12 +39,13 @@ import urllib import google.api_core.exceptions -import google.auth.credentials from google.auth import __version__ as auth_version +import google.auth.credentials from google.auth.exceptions import MutualTLSChannelError import google_auth_httplib2 import httplib2 from parameterized import parameterized +import pytest import uritemplate try: @@ -64,29 +64,46 @@ HAS_UNIVERSE = False from googleapiclient import _helpers as util -from googleapiclient.discovery import (DISCOVERY_URI, - MEDIA_BODY_PARAMETER_DEFAULT_VALUE, - MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE, - STACK_QUERY_PARAMETER_DEFAULT_VALUE, - STACK_QUERY_PARAMETERS, - V1_DISCOVERY_URI, V2_DISCOVERY_URI, - APICoreVersionError, - ResourceMethodParameters, - _fix_up_media_path_base_url, - _fix_up_media_upload, - _fix_up_method_description, - _fix_up_parameters, _urljoin, build, - build_from_document, key2param) +from googleapiclient.discovery import ( + DISCOVERY_URI, + MEDIA_BODY_PARAMETER_DEFAULT_VALUE, + MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE, + STACK_QUERY_PARAMETER_DEFAULT_VALUE, + STACK_QUERY_PARAMETERS, + V1_DISCOVERY_URI, + V2_DISCOVERY_URI, + APICoreVersionError, + ResourceMethodParameters, + _fix_up_media_path_base_url, + _fix_up_media_upload, + _fix_up_method_description, + _fix_up_parameters, + _urljoin, + build, + build_from_document, + key2param, +) from googleapiclient.discovery_cache import DISCOVERY_DOC_MAX_AGE from googleapiclient.discovery_cache.base import Cache -from googleapiclient.errors import (HttpError, InvalidJsonError, - MediaUploadSizeError, ResumableUploadError, - UnacceptableMimeTypeError, - UnknownApiNameOrVersion, UnknownFileType) -from googleapiclient.http import (HttpMock, HttpMockSequence, MediaFileUpload, - MediaIoBaseUpload, MediaUpload, - MediaUploadProgress, build_http, - tunnel_patch) +from googleapiclient.errors import ( + HttpError, + InvalidJsonError, + MediaUploadSizeError, + ResumableUploadError, + UnacceptableMimeTypeError, + UnknownApiNameOrVersion, + UnknownFileType, +) +from googleapiclient.http import ( + HttpMock, + HttpMockSequence, + MediaFileUpload, + MediaIoBaseUpload, + MediaUpload, + MediaUploadProgress, + build_http, + tunnel_patch, +) from googleapiclient.model import JsonModel from googleapiclient.schema import Schemas @@ -141,6 +158,7 @@ def read_datafile(filename, mode="r"): with open(datafile(filename), mode=mode) as f: return f.read() + def parse_version_to_tuple(version_string): """Safely converts a semantic version string to a comparable tuple of integers. @@ -164,6 +182,7 @@ def parse_version_to_tuple(version_string): break return tuple(parts) + class SetupHttplib2(unittest.TestCase): def test_retries(self): # Merely loading googleapiclient.discovery should set the RETRIES to 1. @@ -799,6 +818,7 @@ def test_self_signed_jwt_disabled(self): "cert_configs": {}, } + class DiscoveryFromDocumentMutualTLS(unittest.TestCase): MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials) _reset_universe_domain(MOCK_CREDENTIALS) @@ -905,7 +925,7 @@ def test_mtls_with_provided_client_cert( @parameterized.expand( [ - ("never", "", CONFIG_DATA_WITH_WORKLOAD , REGULAR_ENDPOINT), + ("never", "", CONFIG_DATA_WITH_WORKLOAD, REGULAR_ENDPOINT), ("auto", "", CONFIG_DATA_WITH_WORKLOAD, MTLS_ENDPOINT), ("always", "", CONFIG_DATA_WITH_WORKLOAD, MTLS_ENDPOINT), ("never", "", CONFIG_DATA_WITHOUT_WORKLOAD, REGULAR_ENDPOINT), @@ -914,9 +934,9 @@ def test_mtls_with_provided_client_cert( ] ) @pytest.mark.skipif( - parse_version_to_tuple(auth_version) < (2,43,0), + parse_version_to_tuple(auth_version) < (2, 43, 0), reason="automatic mtls enablement when supported certs present only" - "enabled in google-auth<=2.43.0" + "enabled in google-auth<=2.43.0", ) def test_mtls_with_provided_client_cert_unset_environment_variable( self, use_mtls_env, use_client_cert, config_data, base_url @@ -924,8 +944,8 @@ def test_mtls_with_provided_client_cert_unset_environment_variable( """Tests that mTLS is correctly handled when a client certificate is provided. This test case verifies that when a client certificate is explicitly provided - via `client_options` and GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the - discovery document build process correctly configures the base URL for mTLS + via `client_options` and GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the + discovery document build process correctly configures the base URL for mTLS or regular endpoints based on the `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable. """ if hasattr(google.auth.transport.mtls, "should_use_client_cert"): @@ -941,7 +961,10 @@ def test_mtls_with_provided_client_cert_unset_environment_variable( "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} ): with mock.patch("builtins.open", m): - with mock.patch.dict("os.environ", {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}): + with mock.patch.dict( + "os.environ", + {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}, + ): plus = build_from_document( discovery, credentials=self.MOCK_CREDENTIALS, @@ -1029,6 +1052,7 @@ def test_mtls_with_default_client_cert( self.assertIsNotNone(plus) self.check_http_client_cert(plus, has_client_cert=use_client_cert) self.assertEqual(plus._baseUrl, base_url) + @parameterized.expand( [ ("never", "", CONFIG_DATA_WITH_WORKLOAD, REGULAR_ENDPOINT), @@ -1046,9 +1070,9 @@ def test_mtls_with_default_client_cert( "google.auth.transport.mtls.default_client_encrypted_cert_source", autospec=True ) @pytest.mark.skipif( - parse_version_to_tuple(auth_version) < (2,43,0), + parse_version_to_tuple(auth_version) < (2, 43, 0), reason="automatic mtls enablement when supported certs present only" - "enabled in google-auth<=2.43.0" + "enabled in google-auth<=2.43.0", ) def test_mtls_with_default_client_cert_with_unset_environment_variable( self, @@ -1085,12 +1109,15 @@ def test_mtls_with_default_client_cert_with_unset_environment_variable( "os.environ", {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert} ): with mock.patch("builtins.open", m): - with mock.patch.dict("os.environ", {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}): + with mock.patch.dict( + "os.environ", + {"GOOGLE_API_CERTIFICATE_CONFIG": config_filename}, + ): plus = build_from_document( - discovery, - credentials=self.MOCK_CREDENTIALS, - adc_cert_path=self.ADC_CERT_PATH, - adc_key_path=self.ADC_KEY_PATH, + discovery, + credentials=self.MOCK_CREDENTIALS, + adc_cert_path=self.ADC_CERT_PATH, + adc_key_path=self.ADC_KEY_PATH, ) self.assertIsNotNone(plus) self.assertEqual(plus._baseUrl, base_url)