From 586c80b3ea018c295dbdbbf4e6b49af78c8ed321 Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Tue, 26 May 2020 16:09:33 -0700 Subject: [PATCH 1/8] feat: add mtls feature --- googleapiclient/discovery.py | 99 ++++++++++++++++++++++++++++++++---- noxfile.py | 9 +++- samples/kms/main.py | 56 ++++++++++++++++++++ samples/kms/requirements.txt | 2 + tests/data/bigquery.json | 1 + tests/data/drive.json | 1 + tests/data/latitude.json | 1 + tests/data/logging.json | 1 + tests/data/plus.json | 1 + tests/data/tasks.json | 1 + tests/data/zoo.json | 1 + tests/test_discovery.py | 6 ++- 12 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 samples/kms/main.py create mode 100644 samples/kms/requirements.txt diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index 609ceadf1d9..168b339ef2d 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -1,4 +1,4 @@ -# Copyright 2014 Google Inc. All Rights Reserved. +# Copyright 2020 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,6 +47,13 @@ import httplib2 import uritemplate import google.api_core.client_options +from google.auth.transport import mtls +from google.auth.exceptions import MutualTLSChannelError + +try: + import google_auth_httplib2 +except ImportError: # pragma: NO COVER + google_auth_httplib2 = None # Local imports from googleapiclient import _auth @@ -132,7 +139,7 @@ def fix_method_name(name): Returns: The name with '_' appended if the name is a reserved word and '$' and '-' - replaced with '_'. + replaced with '_'. """ name = name.replace("$", "_").replace("-", "_") if keyword.iskeyword(name) or name in RESERVED_WORDS: @@ -178,6 +185,8 @@ def build( cache_discovery=True, cache=None, client_options=None, + adc_cert_path=None, + adc_key_path=None, ): """Construct a Resource for interacting with an API. @@ -206,9 +215,21 @@ def build( cache object for the discovery documents. client_options: Dictionary or google.api_core.client_options, Client options to set user options on the client. API endpoint should be set through client_options. + client_cert_source is not supported, client cert should be provided using + client_encrypted_cert_source instead. + adc_cert_path: str, client certificate file path to save the application + default client certificate for mTLS. This field is required if you want to + use the default client certificate. + adc_key_path: str, client encrypted private key file path to save the + application default client encrypted private key for mTLS. This field is + required if you want to use the default client certificate. Returns: A Resource object with methods for interacting with the service. + + Raises: + google.auth.exceptions.MutualTLSChannelError: if there are any problems + setting up mutual TLS channel. """ params = {"api": serviceName, "apiVersion": version} @@ -232,7 +253,9 @@ def build( model=model, requestBuilder=requestBuilder, credentials=credentials, - client_options=client_options + client_options=client_options, + adc_cert_path=adc_cert_path, + adc_key_path=adc_key_path, ) except HttpError as e: if e.resp.status == http_client.NOT_FOUND: @@ -309,7 +332,9 @@ def build_from_document( model=None, requestBuilder=HttpRequest, credentials=None, - client_options=None + client_options=None, + adc_cert_path=None, + adc_key_path=None, ): """Create a Resource for interacting with an API. @@ -336,9 +361,21 @@ def build_from_document( authentication. client_options: Dictionary or google.api_core.client_options, Client options to set user options on the client. API endpoint should be set through client_options. + client_cert_source is not supported, client cert should be provided using + client_encrypted_cert_source instead. + adc_cert_path: str, client certificate file path to save the application + default client certificate for mTLS. This field is required if you want to + use the default client certificate. + adc_key_path: str, client encrypted private key file path to save the + application default client encrypted private key for mTLS. This field is + required if you want to use the default client certificate. Returns: A Resource object with methods for interacting with the service. + + Raises: + google.auth.exceptions.MutualTLSChannelError: if there are any problems + setting up mutual TLS channel. """ if http is not None and credentials is not None: @@ -349,7 +386,7 @@ def build_from_document( elif isinstance(service, six.binary_type): service = json.loads(service.decode("utf-8")) - if "rootUrl" not in service and (isinstance(http, (HttpMock, HttpMockSequence))): + if "rootUrl" not in service and isinstance(http, (HttpMock, HttpMockSequence)): logger.error( "You are using HttpMock or HttpMockSequence without" + "having the service discovery doc in cache. Try calling " @@ -359,12 +396,10 @@ def build_from_document( raise InvalidJsonError() # If an API Endpoint is provided on client options, use that as the base URL - base = urljoin(service['rootUrl'], service["servicePath"]) + base = urljoin(service["rootUrl"], service["servicePath"]) if client_options: if type(client_options) == dict: - client_options = google.api_core.client_options.from_dict( - client_options - ) + client_options = google.api_core.client_options.from_dict(client_options) if client_options.api_endpoint: base = client_options.api_endpoint @@ -400,6 +435,52 @@ def build_from_document( else: http = build_http() + # Obtain client cert and create mTLS http channel if cert exists. + client_cert_to_use = None + 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." + ) + if client_options and client_options.client_encrypted_cert_source: + client_cert_to_use = client_options.client_encrypted_cert_source + elif adc_cert_path and adc_key_path and mtls.has_default_client_cert_source(): + client_cert_to_use = mtls.default_client_encrypted_cert_source( + adc_cert_path, adc_key_path + ) + if client_cert_to_use: + cert_path, key_path, passphrase = client_cert_to_use() + + # The http object we built could be google_auth_httplib2.AuthorizedHttp + # or httplib2.Http. In the first case we need to extract the wrapped + # httplib2.Http object from google_auth_httplib2.AuthorizedHttp. + http_channel = ( + http.http + if google_auth_httplib2 + and isinstance(http, google_auth_httplib2.AuthorizedHttp) + else http + ) + http_channel.add_certificate(key_path, cert_path, "", passphrase) + + # If user doesn't provide api endpoint via client options, decide which + # api endpoint to use. + if "mtlsRootUrl" in service and ( + not client_options or not client_options.api_endpoint + ): + mtls_endpoint = urljoin(service["mtlsRootUrl"], service["servicePath"]) + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "Never") + + if not use_mtls_env in ("Never", "Auto", "Always"): + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: Never, Auto, Always" + ) + + # Switch to mTLS endpoint, if environment variable is "Always", or + # environment varibable is "Auto" and client cert exists. + if use_mtls_env == "Always" or ( + use_mtls_env == "Auto" and client_cert_to_use + ): + base = mtls_endpoint + if model is None: features = service.get("features", []) model = JsonModel("dataWrapper" in features) diff --git a/noxfile.py b/noxfile.py index 438ff41c585..cb6901bd0f5 100644 --- a/noxfile.py +++ b/noxfile.py @@ -16,7 +16,6 @@ import nox test_dependencies = [ - "google-auth", "google-auth-httplib2", "mox", "pyopenssl", @@ -54,6 +53,14 @@ def lint(session): ) @nox.session(python=["2.7", "3.5", "3.6", "3.7"]) def unit(session, oauth2client): + session.install( + "-e", + "git+https://github.com/googleapis/google-auth-library-python.git@e8bb68013083961a6421e5f5faf0c7b82a8bf4f1#egg=google-auth", + ) + session.install( + "-e", + "git+https://github.com/googleapis/python-api-core.git@671ad14c30812be4e87ea8771d95fab840982da9#egg=google-api-core", + ) session.install(*test_dependencies) session.install(oauth2client) if session.python < "3.0": diff --git a/samples/kms/main.py b/samples/kms/main.py new file mode 100644 index 00000000000..e58757a0d80 --- /dev/null +++ b/samples/kms/main.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2020 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Simple command-line example for Translate. + +Command-line application that translates some text. +""" +from __future__ import print_function +import google.api_core.client_options +import google.oauth2.credentials + +from googleapiclient.discovery import build + +# project="study-auth-265119" +project = "sijun-mtls-demo" +path = "/usr/local/google/home/sijunliu/wks/google-api-python-client/samples/kms/" +adc_cert_path = path + "cert.pem" +adc_key_path = path + "key.pem" + + +def main(): + cred = google.oauth2.credentials.UserAccessTokenCredentials() + + parent = "projects/{project}/locations/{location}".format( + project=project, location="global" + ) + + # Build a service object for interacting with the API. Visit + # the Google APIs Console + # to get an API key for your own application. + service = build( + "cloudkms", + "v1", + credentials=cred, + adc_cert_path=adc_cert_path, + adc_key_path=adc_key_path, + ) + print(service.projects().locations().keyRings().list(parent=parent).execute()) + + +if __name__ == "__main__": + main() diff --git a/samples/kms/requirements.txt b/samples/kms/requirements.txt new file mode 100644 index 00000000000..815cecf53a4 --- /dev/null +++ b/samples/kms/requirements.txt @@ -0,0 +1,2 @@ +-e git+https://github.com/googleapis/google-auth-library-python.git@e8bb68013083961a6421e5f5faf0c7b82a8bf4f1#egg=google-auth +-e git+https://github.com/googleapis/python-api-core.git@671ad14c30812be4e87ea8771d95fab840982da9#egg=google-api-core diff --git a/tests/data/bigquery.json b/tests/data/bigquery.json index c9f63e38c66..2bfa17373f8 100644 --- a/tests/data/bigquery.json +++ b/tests/data/bigquery.json @@ -19,6 +19,7 @@ "baseUrl": "https://www.googleapis.com/bigquery/v2/", "basePath": "/bigquery/v2/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "bigquery/v2/", "batchPath": "batch", "parameters": { diff --git a/tests/data/drive.json b/tests/data/drive.json index af7b2445cc2..100831f6268 100644 --- a/tests/data/drive.json +++ b/tests/data/drive.json @@ -19,6 +19,7 @@ "baseUrl": "https://www.googleapis.com/drive/v3/", "basePath": "/drive/v3/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "drive/v3/", "batchPath": "batch", "parameters": { diff --git a/tests/data/latitude.json b/tests/data/latitude.json index 7717f904109..84b7acac6c9 100644 --- a/tests/data/latitude.json +++ b/tests/data/latitude.json @@ -14,6 +14,7 @@ "protocol": "rest", "basePath": "/latitude/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "latitude/v1/", "auth": { "oauth2": { diff --git a/tests/data/logging.json b/tests/data/logging.json index b702ea855c3..6bcb3b765e7 100644 --- a/tests/data/logging.json +++ b/tests/data/logging.json @@ -2086,5 +2086,6 @@ "ownerName": "Google", "version": "v2", "rootUrl": "https://logging.googleapis.com/", + "mtlsRootUrl": "https://logging.mtls.googleapis.com/", "kind": "discovery#restDescription" } diff --git a/tests/data/plus.json b/tests/data/plus.json index 36d3ae985a3..8e4815eb0f6 100644 --- a/tests/data/plus.json +++ b/tests/data/plus.json @@ -16,6 +16,7 @@ "protocol": "rest", "basePath": "/plus/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "plus/v1/", "parameters": { "alt": { diff --git a/tests/data/tasks.json b/tests/data/tasks.json index ee7be1013bb..ada88cc9ab2 100644 --- a/tests/data/tasks.json +++ b/tests/data/tasks.json @@ -16,6 +16,7 @@ "protocol": "rest", "basePath": "/tasks/v1/", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "tasks/v1/", "parameters": { "alt": { diff --git a/tests/data/zoo.json b/tests/data/zoo.json index 3a4c77544c3..978da3cb1f9 100644 --- a/tests/data/zoo.json +++ b/tests/data/zoo.json @@ -6,6 +6,7 @@ "basePath": "/zoo/", "batchPath": "batchZoo", "rootUrl": "https://www.googleapis.com/", + "mtlsRootUrl": "https://www.mtls.googleapis.com/", "servicePath": "zoo/v1/", "rpcPath": "/rpc", "parameters": { diff --git a/tests/test_discovery.py b/tests/test_discovery.py index a07e861222a..064486ff189 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -224,7 +224,11 @@ def _base_fix_up_method_description_test( final_max_size, final_media_path_url, ): - fake_root_desc = {"rootUrl": "http://root/", "servicePath": "fake/"} + fake_root_desc = { + "rootUrl": "http://root/", + "servicePath": "fake/", + "mtlsRootUrl": "http://root/", + } fake_path_url = "fake-path/" accept, max_size, media_path_url = _fix_up_media_upload( From d8a3c83c599db87043c113d8f6b1ad251594a9af Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Thu, 28 May 2020 02:06:36 -0700 Subject: [PATCH 2/8] add tests --- tests/test_discovery.py | 98 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 064486ff189..e530738f3ba 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -39,9 +39,12 @@ import sys import unittest2 as unittest +from parameterized import parameterized import mock import google.auth.credentials +from google.auth.transport import mtls +from google.auth.exceptions import MutualTLSChannelError import google_auth_httplib2 from googleapiclient.discovery import _fix_up_media_upload from googleapiclient.discovery import _fix_up_method_description @@ -526,9 +529,7 @@ def test_api_endpoint_override_from_client_options(self): api_endpoint=api_endpoint ) plus = build_from_document( - discovery, - client_options=options, - credentials=self.MOCK_CREDENTIALS + discovery, client_options=options, credentials=self.MOCK_CREDENTIALS ) self.assertEqual(plus._baseUrl, api_endpoint) @@ -537,14 +538,100 @@ def test_api_endpoint_override_from_client_options_dict(self): discovery = open(datafile("plus.json")).read() api_endpoint = "https://foo.googleapis.com/" plus = build_from_document( - discovery, + discovery, client_options={"api_endpoint": api_endpoint}, - credentials=self.MOCK_CREDENTIALS + credentials=self.MOCK_CREDENTIALS, ) self.assertEqual(plus._baseUrl, api_endpoint) +class DiscoveryFromDocumentMutualTLS(unittest.TestCase): + MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials) + ADC_CERT_PATH = "adc_cert_path" + ADC_KEY_PATH = "adc_key_path" + ADC_PASSPHRASE = "adc_passphrase" + REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/" + MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/" + os.environ["GOOGLE_API_USE_MTLS"] = "Never" + + def check_http_client_cert(self, resource, has_client_cert=False): + if isinstance(resource._http, google_auth_httplib2.AuthorizedHttp): + certs = list(resource._http.http.certificates.iter("")) + else: + certs = list(resource._http.certificates.iter("")) + if has_client_cert: + self.assertEqual(len(certs), 1) + self.assertEqual( + certs[0], (self.ADC_KEY_PATH, self.ADC_CERT_PATH, self.ADC_PASSPHRASE) + ) + else: + self.assertEqual(len(certs), 0) + + def client_encrypted_cert_source(self): + return self.ADC_CERT_PATH, self.ADC_KEY_PATH, self.ADC_PASSPHRASE + + def test_mtls_not_trigger_if_http_provided(self): + discovery = open(datafile("plus.json")).read() + plus = build_from_document(discovery, http=httplib2.Http()) + self.assertTrue(plus is not None) + self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + self.check_http_client_cert(plus, has_client_cert=False) + + def test_exception_with_client_cert_source(self): + discovery = open(datafile("plus.json")).read() + with self.assertRaises(MutualTLSChannelError): + build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={"client_cert_source": mock.Mock()}, + ) + + @parameterized.expand(["Never", "Auto", "Always"]) + def test_mtls_with_provided_client_cert(self, google_api_use_mtls): + discovery = open(datafile("plus.json")).read() + os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={ + "client_encrypted_cert_source": self.client_encrypted_cert_source + }, + ) + self.assertTrue(plus is not None) + self.check_http_client_cert(plus, has_client_cert=True) + if google_api_use_mtls != "Never": + self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) + else: + self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + + @parameterized.expand(["Never", "Auto", "Always"]) + def test_mtls_with_default_client_cert(self, google_api_use_mtls): + discovery = open(datafile("plus.json")).read() + os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_encrypted_cert_source", + return_value=self.client_encrypted_cert_source, + ): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + adc_cert_path=self.ADC_CERT_PATH, + adc_key_path=self.ADC_KEY_PATH, + ) + self.assertTrue(plus is not None) + self.check_http_client_cert(plus, has_client_cert=True) + if google_api_use_mtls != "Never": + self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) + else: + self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + + class DiscoveryFromHttp(unittest.TestCase): def setUp(self): self.old_environ = os.environ.copy() @@ -652,7 +739,6 @@ def test_api_endpoint_override_from_client_options_dict(self): class DiscoveryFromAppEngineCache(unittest.TestCase): - def setUp(self): self.old_environ = os.environ.copy() os.environ["APPENGINE_RUNTIME"] = "python27" From b7712e7dde978725b87fbeefec86dd31dc1fd25d Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Thu, 28 May 2020 02:18:39 -0700 Subject: [PATCH 3/8] add tests --- tests/test_discovery.py | 50 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/test_discovery.py b/tests/test_discovery.py index e530738f3ba..d6a39e86373 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -553,7 +553,6 @@ class DiscoveryFromDocumentMutualTLS(unittest.TestCase): ADC_PASSPHRASE = "adc_passphrase" REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/" MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/" - os.environ["GOOGLE_API_USE_MTLS"] = "Never" def check_http_client_cert(self, resource, has_client_cert=False): if isinstance(resource._http, google_auth_httplib2.AuthorizedHttp): @@ -591,6 +590,7 @@ def test_exception_with_client_cert_source(self): def test_mtls_with_provided_client_cert(self, google_api_use_mtls): discovery = open(datafile("plus.json")).read() os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + plus = build_from_document( discovery, credentials=self.MOCK_CREDENTIALS, @@ -605,6 +605,28 @@ def test_mtls_with_provided_client_cert(self, google_api_use_mtls): else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + del os.environ["GOOGLE_API_USE_MTLS"] + + @parameterized.expand(["Never", "Auto", "Always"]) + def test_endpoint_not_switch(self, google_api_use_mtls): + # Test endpoint is not switched if user provided api endpoint + discovery = open(datafile("plus.json")).read() + os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={ + "api_endpoint": "https://foo.googleapis.com", + "client_encrypted_cert_source": self.client_encrypted_cert_source, + }, + ) + self.assertTrue(plus is not None) + self.check_http_client_cert(plus, has_client_cert=True) + self.assertEqual(plus._baseUrl, "https://foo.googleapis.com") + + del os.environ["GOOGLE_API_USE_MTLS"] + @parameterized.expand(["Never", "Auto", "Always"]) def test_mtls_with_default_client_cert(self, google_api_use_mtls): discovery = open(datafile("plus.json")).read() @@ -631,6 +653,32 @@ def test_mtls_with_default_client_cert(self, google_api_use_mtls): else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + del os.environ["GOOGLE_API_USE_MTLS"] + + @parameterized.expand(["Never", "Auto", "Always"]) + def test_mtls_with_no_client_cert(self, google_api_use_mtls): + discovery = open(datafile("plus.json")).read() + os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + adc_cert_path=self.ADC_CERT_PATH, + adc_key_path=self.ADC_KEY_PATH, + ) + self.assertTrue(plus is not None) + self.check_http_client_cert(plus, has_client_cert=False) + if google_api_use_mtls == "Always": + self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) + else: + self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + + del os.environ["GOOGLE_API_USE_MTLS"] + class DiscoveryFromHttp(unittest.TestCase): def setUp(self): From c3188be372c980a4bdb1f4eeeb150bf4e241eb41 Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Thu, 28 May 2020 13:18:37 -0700 Subject: [PATCH 4/8] update google-auth version --- noxfile.py | 7 ++----- setup.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/noxfile.py b/noxfile.py index e1901336296..abc79fc092a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -16,6 +16,7 @@ import nox test_dependencies = [ + "google-auth", "google-auth-httplib2", "mox", "pyopenssl", @@ -53,10 +54,6 @@ def lint(session): ], ) def unit(session, oauth2client): - session.install( - "-e", - "git+https://github.com/googleapis/google-auth-library-python.git@e8bb68013083961a6421e5f5faf0c7b82a8bf4f1#egg=google-auth", - ) session.install( "-e", "git+https://github.com/googleapis/python-api-core.git@671ad14c30812be4e87ea8771d95fab840982da9#egg=google-api-core", @@ -82,4 +79,4 @@ def unit(session, oauth2client): "--cov-fail-under=85", "tests", *session.posargs, - ) \ No newline at end of file + ) diff --git a/setup.py b/setup.py index 4c740e698e6..6554583bba4 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ # currently upgrade their httplib2 version. # Please see https://github.com/googleapis/google-api-python-client/pull/841 "httplib2>=0.9.2,<1dev", - "google-auth>=1.4.1", + "google-auth>=1.16.0", "google-auth-httplib2>=0.0.3", "google-api-core>=1.13.0,<2dev", "six>=1.6.1,<2dev", From 35c82b8627f3385ea6af42050cc6a9e8fa3097f3 Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Thu, 28 May 2020 15:26:10 -0700 Subject: [PATCH 5/8] update api-core version --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index abc79fc092a..3380efe8296 100644 --- a/noxfile.py +++ b/noxfile.py @@ -56,7 +56,7 @@ def lint(session): def unit(session, oauth2client): session.install( "-e", - "git+https://github.com/googleapis/python-api-core.git@671ad14c30812be4e87ea8771d95fab840982da9#egg=google-api-core", + "git+https://github.com/googleapis/python-api-core.git@master#egg=google-api-core", ) session.install(*test_dependencies) session.install(oauth2client) From a876cb975d2bf81a823787b61431a12c30212e65 Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Thu, 28 May 2020 16:32:17 -0700 Subject: [PATCH 6/8] update --- googleapiclient/discovery.py | 12 +++++++----- noxfile.py | 1 + samples/cloudkms/README | 3 +++ samples/{kms => cloudkms}/main.py | 32 ++++++++++++++++++++----------- samples/kms/requirements.txt | 2 -- tests/test_discovery.py | 30 ++++++++++++++--------------- 6 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 samples/cloudkms/README rename samples/{kms => cloudkms}/main.py (50%) delete mode 100644 samples/kms/requirements.txt diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index 168b339ef2d..a5decbf7f81 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -467,17 +467,19 @@ def build_from_document( not client_options or not client_options.api_endpoint ): mtls_endpoint = urljoin(service["mtlsRootUrl"], service["servicePath"]) - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "Never") + use_client_cert_env = os.getenv( + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "Never" + ) - if not use_mtls_env in ("Never", "Auto", "Always"): + if not use_client_cert_env in ("Never", "Auto", "Always"): raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: Never, Auto, Always" + "Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: Never, Auto, Always" ) # Switch to mTLS endpoint, if environment variable is "Always", or # environment varibable is "Auto" and client cert exists. - if use_mtls_env == "Always" or ( - use_mtls_env == "Auto" and client_cert_to_use + if use_client_cert_env == "Always" or ( + use_client_cert_env == "Auto" and client_cert_to_use ): base = mtls_endpoint diff --git a/noxfile.py b/noxfile.py index 3380efe8296..ced9cf57632 100644 --- a/noxfile.py +++ b/noxfile.py @@ -19,6 +19,7 @@ "google-auth", "google-auth-httplib2", "mox", + "parameterized", "pyopenssl", "pytest", "pytest-cov", diff --git a/samples/cloudkms/README b/samples/cloudkms/README new file mode 100644 index 00000000000..d15a740dc8a --- /dev/null +++ b/samples/cloudkms/README @@ -0,0 +1,3 @@ +.Cloud KMS mutual TLS example to list key rings in global location. + +api: cloudkms diff --git a/samples/kms/main.py b/samples/cloudkms/main.py similarity index 50% rename from samples/kms/main.py rename to samples/cloudkms/main.py index e58757a0d80..130748c6304 100644 --- a/samples/kms/main.py +++ b/samples/cloudkms/main.py @@ -15,9 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Simple command-line example for Translate. +"""Mutual TLS example for Cloud KMS. -Command-line application that translates some text. +This application returns the list of key rings in global location. First fill in +the project and adc_cert_key_folder value, then set the GOOGLE_API_USE_CLIENT_CERTIFICATE +environment variable to one of the following values based on your need: +(1) "Never": This means we always use regular api endpoint. Since this is the +default value, there is no need to explicitly set it. +(2) "Always": This means we always use the mutual TLS api endpoint. +(3) "Auto": This means we auto switch to mutual TLS api endpoint, if the ADC +client cert and key are detected. """ from __future__ import print_function import google.api_core.client_options @@ -25,11 +32,14 @@ from googleapiclient.discovery import build -# project="study-auth-265119" -project = "sijun-mtls-demo" -path = "/usr/local/google/home/sijunliu/wks/google-api-python-client/samples/kms/" -adc_cert_path = path + "cert.pem" -adc_key_path = path + "key.pem" +project = "FILL IN YOUR PROJECT ID" + +# The following are the file paths to save ADC client cert and key. If you don't +# want to use mutual TLS, simply ignore adc_cert_key_folder, and set adc_cert_path +# and adc_key_path to None. +adc_cert_key_folder = "FILL IN THE PATH WHERE YOU WANT TO SAVE ADC CERT AND KEY" +adc_cert_path = adc_cert_key_folder + "cert.pem" +adc_key_path = adc_cert_key_folder + "key.pem" def main(): @@ -39,9 +49,7 @@ def main(): project=project, location="global" ) - # Build a service object for interacting with the API. Visit - # the Google APIs Console - # to get an API key for your own application. + # Build a service object for interacting with the API. service = build( "cloudkms", "v1", @@ -49,7 +57,9 @@ def main(): adc_cert_path=adc_cert_path, adc_key_path=adc_key_path, ) - print(service.projects().locations().keyRings().list(parent=parent).execute()) + + # Return the list of key rings in global location. + return service.projects().locations().keyRings().list(parent=parent).execute() if __name__ == "__main__": diff --git a/samples/kms/requirements.txt b/samples/kms/requirements.txt deleted file mode 100644 index 815cecf53a4..00000000000 --- a/samples/kms/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ --e git+https://github.com/googleapis/google-auth-library-python.git@e8bb68013083961a6421e5f5faf0c7b82a8bf4f1#egg=google-auth --e git+https://github.com/googleapis/python-api-core.git@671ad14c30812be4e87ea8771d95fab840982da9#egg=google-api-core diff --git a/tests/test_discovery.py b/tests/test_discovery.py index d6a39e86373..15217207fb8 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -587,9 +587,9 @@ def test_exception_with_client_cert_source(self): ) @parameterized.expand(["Never", "Auto", "Always"]) - def test_mtls_with_provided_client_cert(self, google_api_use_mtls): + def test_mtls_with_provided_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert plus = build_from_document( discovery, @@ -600,18 +600,18 @@ def test_mtls_with_provided_client_cert(self, google_api_use_mtls): ) self.assertTrue(plus is not None) self.check_http_client_cert(plus, has_client_cert=True) - if google_api_use_mtls != "Never": + if use_client_cert != "Never": self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - del os.environ["GOOGLE_API_USE_MTLS"] + del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] @parameterized.expand(["Never", "Auto", "Always"]) - def test_endpoint_not_switch(self, google_api_use_mtls): + def test_endpoint_not_switch(self, use_client_cert): # Test endpoint is not switched if user provided api endpoint discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert plus = build_from_document( discovery, @@ -625,12 +625,12 @@ def test_endpoint_not_switch(self, google_api_use_mtls): self.check_http_client_cert(plus, has_client_cert=True) self.assertEqual(plus._baseUrl, "https://foo.googleapis.com") - del os.environ["GOOGLE_API_USE_MTLS"] + del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] @parameterized.expand(["Never", "Auto", "Always"]) - def test_mtls_with_default_client_cert(self, google_api_use_mtls): + def test_mtls_with_default_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", @@ -648,17 +648,17 @@ def test_mtls_with_default_client_cert(self, google_api_use_mtls): ) self.assertTrue(plus is not None) self.check_http_client_cert(plus, has_client_cert=True) - if google_api_use_mtls != "Never": + if use_client_cert != "Never": self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - del os.environ["GOOGLE_API_USE_MTLS"] + del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] @parameterized.expand(["Never", "Auto", "Always"]) - def test_mtls_with_no_client_cert(self, google_api_use_mtls): + def test_mtls_with_no_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = google_api_use_mtls + os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", @@ -672,12 +672,12 @@ def test_mtls_with_no_client_cert(self, google_api_use_mtls): ) self.assertTrue(plus is not None) self.check_http_client_cert(plus, has_client_cert=False) - if google_api_use_mtls == "Always": + if use_client_cert == "Always": self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - del os.environ["GOOGLE_API_USE_MTLS"] + del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] class DiscoveryFromHttp(unittest.TestCase): From aa3a827495eb1a1e1baa6061abd62ea56ad4b817 Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Fri, 29 May 2020 13:18:51 -0700 Subject: [PATCH 7/8] fix the env name --- googleapiclient/discovery.py | 12 +++++------- samples/cloudkms/main.py | 2 +- tests/test_discovery.py | 16 ++++++++-------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index a5decbf7f81..168b339ef2d 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -467,19 +467,17 @@ def build_from_document( not client_options or not client_options.api_endpoint ): mtls_endpoint = urljoin(service["mtlsRootUrl"], service["servicePath"]) - use_client_cert_env = os.getenv( - "GOOGLE_API_USE_CLIENT_CERTIFICATE", "Never" - ) + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "Never") - if not use_client_cert_env in ("Never", "Auto", "Always"): + if not use_mtls_env in ("Never", "Auto", "Always"): raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: Never, Auto, Always" + "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: Never, Auto, Always" ) # Switch to mTLS endpoint, if environment variable is "Always", or # environment varibable is "Auto" and client cert exists. - if use_client_cert_env == "Always" or ( - use_client_cert_env == "Auto" and client_cert_to_use + if use_mtls_env == "Always" or ( + use_mtls_env == "Auto" and client_cert_to_use ): base = mtls_endpoint diff --git a/samples/cloudkms/main.py b/samples/cloudkms/main.py index 130748c6304..2f882ebb57e 100644 --- a/samples/cloudkms/main.py +++ b/samples/cloudkms/main.py @@ -18,7 +18,7 @@ """Mutual TLS example for Cloud KMS. This application returns the list of key rings in global location. First fill in -the project and adc_cert_key_folder value, then set the GOOGLE_API_USE_CLIENT_CERTIFICATE +the project and adc_cert_key_folder value, then set the GOOGLE_API_USE_MTLS environment variable to one of the following values based on your need: (1) "Never": This means we always use regular api endpoint. Since this is the default value, there is no need to explicitly set it. diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 15217207fb8..e3ff9d79558 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -589,7 +589,7 @@ def test_exception_with_client_cert_source(self): @parameterized.expand(["Never", "Auto", "Always"]) def test_mtls_with_provided_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert + os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert plus = build_from_document( discovery, @@ -605,13 +605,13 @@ def test_mtls_with_provided_client_cert(self, use_client_cert): else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] + del os.environ["GOOGLE_API_USE_MTLS"] @parameterized.expand(["Never", "Auto", "Always"]) def test_endpoint_not_switch(self, use_client_cert): # Test endpoint is not switched if user provided api endpoint discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert + os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert plus = build_from_document( discovery, @@ -625,12 +625,12 @@ def test_endpoint_not_switch(self, use_client_cert): self.check_http_client_cert(plus, has_client_cert=True) self.assertEqual(plus._baseUrl, "https://foo.googleapis.com") - del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] + del os.environ["GOOGLE_API_USE_MTLS"] @parameterized.expand(["Never", "Auto", "Always"]) def test_mtls_with_default_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert + os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", @@ -653,12 +653,12 @@ def test_mtls_with_default_client_cert(self, use_client_cert): else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] + del os.environ["GOOGLE_API_USE_MTLS"] @parameterized.expand(["Never", "Auto", "Always"]) def test_mtls_with_no_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] = use_client_cert + os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", @@ -677,7 +677,7 @@ def test_mtls_with_no_client_cert(self, use_client_cert): else: self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - del os.environ["GOOGLE_API_USE_CLIENT_CERTIFICATE"] + del os.environ["GOOGLE_API_USE_MTLS"] class DiscoveryFromHttp(unittest.TestCase): From 8e7f304a454f2ccb22bbbb029b9a123efe04c00e Mon Sep 17 00:00:00 2001 From: Sijun Liu Date: Fri, 29 May 2020 17:42:18 -0700 Subject: [PATCH 8/8] update --- googleapiclient/discovery.py | 2 +- samples/cloudkms/README | 3 - samples/cloudkms/main.py | 66 -------------- tests/test_discovery.py | 171 +++++++++++++++++++---------------- 4 files changed, 93 insertions(+), 149 deletions(-) delete mode 100644 samples/cloudkms/README delete mode 100644 samples/cloudkms/main.py diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py index 168b339ef2d..115609f3e4a 100644 --- a/googleapiclient/discovery.py +++ b/googleapiclient/discovery.py @@ -1,4 +1,4 @@ -# Copyright 2020 Google Inc. All Rights Reserved. +# Copyright 2014 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/samples/cloudkms/README b/samples/cloudkms/README deleted file mode 100644 index d15a740dc8a..00000000000 --- a/samples/cloudkms/README +++ /dev/null @@ -1,3 +0,0 @@ -.Cloud KMS mutual TLS example to list key rings in global location. - -api: cloudkms diff --git a/samples/cloudkms/main.py b/samples/cloudkms/main.py deleted file mode 100644 index 2f882ebb57e..00000000000 --- a/samples/cloudkms/main.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2020 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Mutual TLS example for Cloud KMS. - -This application returns the list of key rings in global location. First fill in -the project and adc_cert_key_folder value, then set the GOOGLE_API_USE_MTLS -environment variable to one of the following values based on your need: -(1) "Never": This means we always use regular api endpoint. Since this is the -default value, there is no need to explicitly set it. -(2) "Always": This means we always use the mutual TLS api endpoint. -(3) "Auto": This means we auto switch to mutual TLS api endpoint, if the ADC -client cert and key are detected. -""" -from __future__ import print_function -import google.api_core.client_options -import google.oauth2.credentials - -from googleapiclient.discovery import build - -project = "FILL IN YOUR PROJECT ID" - -# The following are the file paths to save ADC client cert and key. If you don't -# want to use mutual TLS, simply ignore adc_cert_key_folder, and set adc_cert_path -# and adc_key_path to None. -adc_cert_key_folder = "FILL IN THE PATH WHERE YOU WANT TO SAVE ADC CERT AND KEY" -adc_cert_path = adc_cert_key_folder + "cert.pem" -adc_key_path = adc_cert_key_folder + "key.pem" - - -def main(): - cred = google.oauth2.credentials.UserAccessTokenCredentials() - - parent = "projects/{project}/locations/{location}".format( - project=project, location="global" - ) - - # Build a service object for interacting with the API. - service = build( - "cloudkms", - "v1", - credentials=cred, - adc_cert_path=adc_cert_path, - adc_key_path=adc_key_path, - ) - - # Return the list of key rings in global location. - return service.projects().locations().keyRings().list(parent=parent).execute() - - -if __name__ == "__main__": - main() diff --git a/tests/test_discovery.py b/tests/test_discovery.py index e3ff9d79558..9015066006a 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -452,7 +452,7 @@ def test_can_build_from_local_document(self): base="https://www.googleapis.com/", credentials=self.MOCK_CREDENTIALS, ) - self.assertTrue(plus is not None) + self.assertIsNotNone(plus) self.assertTrue(hasattr(plus, "activities")) def test_can_build_from_local_deserialized_document(self): @@ -463,7 +463,7 @@ def test_can_build_from_local_deserialized_document(self): base="https://www.googleapis.com/", credentials=self.MOCK_CREDENTIALS, ) - self.assertTrue(plus is not None) + self.assertIsNotNone(plus) self.assertTrue(hasattr(plus, "activities")) def test_building_with_base_remembers_base(self): @@ -546,13 +546,15 @@ def test_api_endpoint_override_from_client_options_dict(self): self.assertEqual(plus._baseUrl, api_endpoint) +REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/" +MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/" + + class DiscoveryFromDocumentMutualTLS(unittest.TestCase): MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials) ADC_CERT_PATH = "adc_cert_path" ADC_KEY_PATH = "adc_key_path" ADC_PASSPHRASE = "adc_passphrase" - REGULAR_ENDPOINT = "https://www.googleapis.com/plus/v1/" - MTLS_ENDPOINT = "https://www.mtls.googleapis.com/plus/v1/" def check_http_client_cert(self, resource, has_client_cert=False): if isinstance(resource._http, google_auth_httplib2.AuthorizedHttp): @@ -573,8 +575,8 @@ def client_encrypted_cert_source(self): def test_mtls_not_trigger_if_http_provided(self): discovery = open(datafile("plus.json")).read() plus = build_from_document(discovery, http=httplib2.Http()) - self.assertTrue(plus is not None) - self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) + self.assertIsNotNone(plus) + self.assertEqual(plus._baseUrl, REGULAR_ENDPOINT) self.check_http_client_cert(plus, has_client_cert=False) def test_exception_with_client_cert_source(self): @@ -586,98 +588,109 @@ def test_exception_with_client_cert_source(self): client_options={"client_cert_source": mock.Mock()}, ) - @parameterized.expand(["Never", "Auto", "Always"]) - def test_mtls_with_provided_client_cert(self, use_client_cert): + @parameterized.expand( + [ + ("Never", REGULAR_ENDPOINT), + ("Auto", MTLS_ENDPOINT), + ("Always", MTLS_ENDPOINT), + ] + ) + def test_mtls_with_provided_client_cert(self, use_mtls_env, base_url): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert - plus = build_from_document( - discovery, - credentials=self.MOCK_CREDENTIALS, - client_options={ - "client_encrypted_cert_source": self.client_encrypted_cert_source - }, - ) - self.assertTrue(plus is not None) - self.check_http_client_cert(plus, has_client_cert=True) - if use_client_cert != "Never": - self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) - else: - self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - - del os.environ["GOOGLE_API_USE_MTLS"] + with mock.patch.dict("os.environ", {"GOOGLE_API_USE_MTLS": use_mtls_env}): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={ + "client_encrypted_cert_source": self.client_encrypted_cert_source + }, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert=True) + self.assertEqual(plus._baseUrl, base_url) @parameterized.expand(["Never", "Auto", "Always"]) - def test_endpoint_not_switch(self, use_client_cert): + def test_endpoint_not_switch(self, use_mtls_env): # Test endpoint is not switched if user provided api endpoint discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert - plus = build_from_document( - discovery, - credentials=self.MOCK_CREDENTIALS, - client_options={ - "api_endpoint": "https://foo.googleapis.com", - "client_encrypted_cert_source": self.client_encrypted_cert_source, - }, + with mock.patch.dict("os.environ", {"GOOGLE_API_USE_MTLS": use_mtls_env}): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + client_options={ + "api_endpoint": "https://foo.googleapis.com", + "client_encrypted_cert_source": self.client_encrypted_cert_source, + }, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert=True) + self.assertEqual(plus._baseUrl, "https://foo.googleapis.com") + + @parameterized.expand( + [ + ("Never", REGULAR_ENDPOINT), + ("Auto", MTLS_ENDPOINT), + ("Always", MTLS_ENDPOINT), + ] + ) + @mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", autospec=True + ) + @mock.patch( + "google.auth.transport.mtls.default_client_encrypted_cert_source", autospec=True + ) + def test_mtls_with_default_client_cert( + self, + use_mtls_env, + base_url, + default_client_encrypted_cert_source, + has_default_client_cert_source, + ): + has_default_client_cert_source.return_value = True + default_client_encrypted_cert_source.return_value = ( + self.client_encrypted_cert_source ) - self.assertTrue(plus is not None) - self.check_http_client_cert(plus, has_client_cert=True) - self.assertEqual(plus._baseUrl, "https://foo.googleapis.com") - - del os.environ["GOOGLE_API_USE_MTLS"] - - @parameterized.expand(["Never", "Auto", "Always"]) - def test_mtls_with_default_client_cert(self, use_client_cert): discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert - with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, - ): - with mock.patch( - "google.auth.transport.mtls.default_client_encrypted_cert_source", - return_value=self.client_encrypted_cert_source, - ): - plus = build_from_document( - discovery, - credentials=self.MOCK_CREDENTIALS, - adc_cert_path=self.ADC_CERT_PATH, - adc_key_path=self.ADC_KEY_PATH, - ) - self.assertTrue(plus is not None) - self.check_http_client_cert(plus, has_client_cert=True) - if use_client_cert != "Never": - self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) - else: - self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - - del os.environ["GOOGLE_API_USE_MTLS"] - - @parameterized.expand(["Never", "Auto", "Always"]) - def test_mtls_with_no_client_cert(self, use_client_cert): + with mock.patch.dict("os.environ", {"GOOGLE_API_USE_MTLS": use_mtls_env}): + plus = build_from_document( + discovery, + credentials=self.MOCK_CREDENTIALS, + adc_cert_path=self.ADC_CERT_PATH, + adc_key_path=self.ADC_KEY_PATH, + ) + self.assertIsNotNone(plus) + self.check_http_client_cert(plus, has_client_cert=True) + self.assertEqual(plus._baseUrl, base_url) + + @parameterized.expand( + [ + ("Never", REGULAR_ENDPOINT), + ("Auto", REGULAR_ENDPOINT), + ("Always", MTLS_ENDPOINT), + ] + ) + @mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", autospec=True + ) + def test_mtls_with_no_client_cert( + self, use_mtls_env, base_url, has_default_client_cert_source + ): + has_default_client_cert_source.return_value = False discovery = open(datafile("plus.json")).read() - os.environ["GOOGLE_API_USE_MTLS"] = use_client_cert - with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, - ): + with mock.patch.dict("os.environ", {"GOOGLE_API_USE_MTLS": use_mtls_env}): plus = build_from_document( discovery, credentials=self.MOCK_CREDENTIALS, adc_cert_path=self.ADC_CERT_PATH, adc_key_path=self.ADC_KEY_PATH, ) - self.assertTrue(plus is not None) + self.assertIsNotNone(plus) self.check_http_client_cert(plus, has_client_cert=False) - if use_client_cert == "Always": - self.assertEqual(plus._baseUrl, self.MTLS_ENDPOINT) - else: - self.assertEqual(plus._baseUrl, self.REGULAR_ENDPOINT) - - del os.environ["GOOGLE_API_USE_MTLS"] + self.assertEqual(plus._baseUrl, base_url) class DiscoveryFromHttp(unittest.TestCase):