Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)


Expand Down
2 changes: 1 addition & 1 deletion googleapiclient/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
2 changes: 1 addition & 1 deletion googleapiclient/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
99 changes: 63 additions & 36 deletions tests/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@
import json
import os
import pickle
import pytest
import re
import sys
import unittest
from unittest import mock
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:
Expand All @@ -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

Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand All @@ -914,18 +934,18 @@ 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
):
"""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"):
Expand All @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down