4747import httplib2
4848import uritemplate
4949import google .api_core .client_options
50+ from google .auth .transport import mtls
51+ from google .auth .exceptions import MutualTLSChannelError
52+
53+ try :
54+ import google_auth_httplib2
55+ except ImportError : # pragma: NO COVER
56+ google_auth_httplib2 = None
5057
5158# Local imports
5259from googleapiclient import _auth
@@ -132,7 +139,7 @@ def fix_method_name(name):
132139
133140 Returns:
134141 The name with '_' appended if the name is a reserved word and '$' and '-'
135- replaced with '_'.
142+ replaced with '_'.
136143 """
137144 name = name .replace ("$" , "_" ).replace ("-" , "_" )
138145 if keyword .iskeyword (name ) or name in RESERVED_WORDS :
@@ -178,6 +185,8 @@ def build(
178185 cache_discovery = True ,
179186 cache = None ,
180187 client_options = None ,
188+ adc_cert_path = None ,
189+ adc_key_path = None ,
181190):
182191 """Construct a Resource for interacting with an API.
183192
@@ -206,9 +215,21 @@ def build(
206215 cache object for the discovery documents.
207216 client_options: Dictionary or google.api_core.client_options, Client options to set user
208217 options on the client. API endpoint should be set through client_options.
218+ client_cert_source is not supported, client cert should be provided using
219+ client_encrypted_cert_source instead.
220+ adc_cert_path: str, client certificate file path to save the application
221+ default client certificate for mTLS. This field is required if you want to
222+ use the default client certificate.
223+ adc_key_path: str, client encrypted private key file path to save the
224+ application default client encrypted private key for mTLS. This field is
225+ required if you want to use the default client certificate.
209226
210227 Returns:
211228 A Resource object with methods for interacting with the service.
229+
230+ Raises:
231+ google.auth.exceptions.MutualTLSChannelError: if there are any problems
232+ setting up mutual TLS channel.
212233 """
213234 params = {"api" : serviceName , "apiVersion" : version }
214235
@@ -232,7 +253,9 @@ def build(
232253 model = model ,
233254 requestBuilder = requestBuilder ,
234255 credentials = credentials ,
235- client_options = client_options
256+ client_options = client_options ,
257+ adc_cert_path = adc_cert_path ,
258+ adc_key_path = adc_key_path ,
236259 )
237260 except HttpError as e :
238261 if e .resp .status == http_client .NOT_FOUND :
@@ -309,7 +332,9 @@ def build_from_document(
309332 model = None ,
310333 requestBuilder = HttpRequest ,
311334 credentials = None ,
312- client_options = None
335+ client_options = None ,
336+ adc_cert_path = None ,
337+ adc_key_path = None ,
313338):
314339 """Create a Resource for interacting with an API.
315340
@@ -336,9 +361,21 @@ def build_from_document(
336361 authentication.
337362 client_options: Dictionary or google.api_core.client_options, Client options to set user
338363 options on the client. API endpoint should be set through client_options.
364+ client_cert_source is not supported, client cert should be provided using
365+ client_encrypted_cert_source instead.
366+ adc_cert_path: str, client certificate file path to save the application
367+ default client certificate for mTLS. This field is required if you want to
368+ use the default client certificate.
369+ adc_key_path: str, client encrypted private key file path to save the
370+ application default client encrypted private key for mTLS. This field is
371+ required if you want to use the default client certificate.
339372
340373 Returns:
341374 A Resource object with methods for interacting with the service.
375+
376+ Raises:
377+ google.auth.exceptions.MutualTLSChannelError: if there are any problems
378+ setting up mutual TLS channel.
342379 """
343380
344381 if http is not None and credentials is not None :
@@ -349,7 +386,7 @@ def build_from_document(
349386 elif isinstance (service , six .binary_type ):
350387 service = json .loads (service .decode ("utf-8" ))
351388
352- if "rootUrl" not in service and ( isinstance (http , (HttpMock , HttpMockSequence ) )):
389+ if "rootUrl" not in service and isinstance (http , (HttpMock , HttpMockSequence )):
353390 logger .error (
354391 "You are using HttpMock or HttpMockSequence without"
355392 + "having the service discovery doc in cache. Try calling "
@@ -359,12 +396,10 @@ def build_from_document(
359396 raise InvalidJsonError ()
360397
361398 # If an API Endpoint is provided on client options, use that as the base URL
362- base = urljoin (service [' rootUrl' ], service ["servicePath" ])
399+ base = urljoin (service [" rootUrl" ], service ["servicePath" ])
363400 if client_options :
364401 if type (client_options ) == dict :
365- client_options = google .api_core .client_options .from_dict (
366- client_options
367- )
402+ client_options = google .api_core .client_options .from_dict (client_options )
368403 if client_options .api_endpoint :
369404 base = client_options .api_endpoint
370405
@@ -400,6 +435,52 @@ def build_from_document(
400435 else :
401436 http = build_http ()
402437
438+ # Obtain client cert and create mTLS http channel if cert exists.
439+ client_cert_to_use = None
440+ if client_options and client_options .client_cert_source :
441+ raise MutualTLSChannelError (
442+ "ClientOptions.client_cert_source is not supported, please use ClientOptions.client_encrypted_cert_source."
443+ )
444+ if client_options and client_options .client_encrypted_cert_source :
445+ client_cert_to_use = client_options .client_encrypted_cert_source
446+ elif adc_cert_path and adc_key_path and mtls .has_default_client_cert_source ():
447+ client_cert_to_use = mtls .default_client_encrypted_cert_source (
448+ adc_cert_path , adc_key_path
449+ )
450+ if client_cert_to_use :
451+ cert_path , key_path , passphrase = client_cert_to_use ()
452+
453+ # The http object we built could be google_auth_httplib2.AuthorizedHttp
454+ # or httplib2.Http. In the first case we need to extract the wrapped
455+ # httplib2.Http object from google_auth_httplib2.AuthorizedHttp.
456+ http_channel = (
457+ http .http
458+ if google_auth_httplib2
459+ and isinstance (http , google_auth_httplib2 .AuthorizedHttp )
460+ else http
461+ )
462+ http_channel .add_certificate (key_path , cert_path , "" , passphrase )
463+
464+ # If user doesn't provide api endpoint via client options, decide which
465+ # api endpoint to use.
466+ if "mtlsRootUrl" in service and (
467+ not client_options or not client_options .api_endpoint
468+ ):
469+ mtls_endpoint = urljoin (service ["mtlsRootUrl" ], service ["servicePath" ])
470+ use_mtls_env = os .getenv ("GOOGLE_API_USE_MTLS" , "Never" )
471+
472+ if not use_mtls_env in ("Never" , "Auto" , "Always" ):
473+ raise MutualTLSChannelError (
474+ "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: Never, Auto, Always"
475+ )
476+
477+ # Switch to mTLS endpoint, if environment variable is "Always", or
478+ # environment varibable is "Auto" and client cert exists.
479+ if use_mtls_env == "Always" or (
480+ use_mtls_env == "Auto" and client_cert_to_use
481+ ):
482+ base = mtls_endpoint
483+
403484 if model is None :
404485 features = service .get ("features" , [])
405486 model = JsonModel ("dataWrapper" in features )
0 commit comments