47
47
import httplib2
48
48
import uritemplate
49
49
import 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
50
57
51
58
# Local imports
52
59
from googleapiclient import _auth
@@ -132,7 +139,7 @@ def fix_method_name(name):
132
139
133
140
Returns:
134
141
The name with '_' appended if the name is a reserved word and '$' and '-'
135
- replaced with '_'.
142
+ replaced with '_'.
136
143
"""
137
144
name = name .replace ("$" , "_" ).replace ("-" , "_" )
138
145
if keyword .iskeyword (name ) or name in RESERVED_WORDS :
@@ -178,6 +185,8 @@ def build(
178
185
cache_discovery = True ,
179
186
cache = None ,
180
187
client_options = None ,
188
+ adc_cert_path = None ,
189
+ adc_key_path = None ,
181
190
):
182
191
"""Construct a Resource for interacting with an API.
183
192
@@ -206,9 +215,21 @@ def build(
206
215
cache object for the discovery documents.
207
216
client_options: Dictionary or google.api_core.client_options, Client options to set user
208
217
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.
209
226
210
227
Returns:
211
228
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.
212
233
"""
213
234
params = {"api" : serviceName , "apiVersion" : version }
214
235
@@ -232,7 +253,9 @@ def build(
232
253
model = model ,
233
254
requestBuilder = requestBuilder ,
234
255
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 ,
236
259
)
237
260
except HttpError as e :
238
261
if e .resp .status == http_client .NOT_FOUND :
@@ -309,7 +332,9 @@ def build_from_document(
309
332
model = None ,
310
333
requestBuilder = HttpRequest ,
311
334
credentials = None ,
312
- client_options = None
335
+ client_options = None ,
336
+ adc_cert_path = None ,
337
+ adc_key_path = None ,
313
338
):
314
339
"""Create a Resource for interacting with an API.
315
340
@@ -336,9 +361,21 @@ def build_from_document(
336
361
authentication.
337
362
client_options: Dictionary or google.api_core.client_options, Client options to set user
338
363
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.
339
372
340
373
Returns:
341
374
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.
342
379
"""
343
380
344
381
if http is not None and credentials is not None :
@@ -349,7 +386,7 @@ def build_from_document(
349
386
elif isinstance (service , six .binary_type ):
350
387
service = json .loads (service .decode ("utf-8" ))
351
388
352
- if "rootUrl" not in service and ( isinstance (http , (HttpMock , HttpMockSequence ) )):
389
+ if "rootUrl" not in service and isinstance (http , (HttpMock , HttpMockSequence )):
353
390
logger .error (
354
391
"You are using HttpMock or HttpMockSequence without"
355
392
+ "having the service discovery doc in cache. Try calling "
@@ -359,12 +396,10 @@ def build_from_document(
359
396
raise InvalidJsonError ()
360
397
361
398
# 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" ])
363
400
if client_options :
364
401
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 )
368
403
if client_options .api_endpoint :
369
404
base = client_options .api_endpoint
370
405
@@ -400,6 +435,52 @@ def build_from_document(
400
435
else :
401
436
http = build_http ()
402
437
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
+
403
484
if model is None :
404
485
features = service .get ("features" , [])
405
486
model = JsonModel ("dataWrapper" in features )
0 commit comments