Skip to content
Permalink
Browse files
feat: add mtls feature (#917)
  • Loading branch information
arithmetic1728 committed Jun 2, 2020
1 parent c482712 commit 981eadf7cfdb576981d92fcda498c76422821426
Showing with 263 additions and 19 deletions.
  1. +89 鈭8 googleapiclient/discovery.py
  2. +6 鈭1 noxfile.py
  3. +1 鈭1 setup.py
  4. +1 鈭0 tests/data/bigquery.json
  5. +1 鈭0 tests/data/drive.json
  6. +1 鈭0 tests/data/latitude.json
  7. +1 鈭0 tests/data/logging.json
  8. +1 鈭0 tests/data/plus.json
  9. +1 鈭0 tests/data/tasks.json
  10. +1 鈭0 tests/data/zoo.json
  11. +160 鈭9 tests/test_discovery.py
@@ -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)
@@ -19,6 +19,7 @@
"google-auth",
"google-auth-httplib2",
"mox",
"parameterized",
"pyopenssl",
"pytest",
"pytest-cov",
@@ -54,6 +55,10 @@ def lint(session):
],
)
def unit(session, oauth2client):
session.install(
"-e",
"git+https://github.com/googleapis/python-api-core.git@master#egg=google-api-core",
)
session.install(*test_dependencies)
session.install(oauth2client)
if session.python < "3.0":
@@ -75,4 +80,4 @@ def unit(session, oauth2client):
"--cov-fail-under=85",
"tests",
*session.posargs,
)
)
@@ -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",
@@ -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": {
@@ -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": {
@@ -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": {
@@ -2086,5 +2086,6 @@
"ownerName": "Google",
"version": "v2",
"rootUrl": "https://logging.googleapis.com/",
"mtlsRootUrl": "https://logging.mtls.googleapis.com/",
"kind": "discovery#restDescription"
}
@@ -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": {
@@ -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": {
@@ -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": {

0 comments on commit 981eadf

Please sign in to comment.