Content-Length: 707902 | pFad | https://www.github.com/googleapis/python-cloud-core/commit/a93129bf2fa6fabda3de8b8a342f31a328ccbaf0

2FD feat: add mtls support (#75) · googleapis/python-cloud-core@a93129b · GitHub
Skip to content

Commit a93129b

Browse files
feat: add mtls support (#75)
* feat: add mtls support * update * update * update * chore: update * update Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com>
1 parent a0a4a28 commit a93129b

File tree

5 files changed

+106
-10
lines changed

5 files changed

+106
-10
lines changed

google/cloud/_http.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
except ImportError:
2121
import collections as collections_abc
2222
import json
23+
import os
2324
import platform
2425
import warnings
2526

@@ -176,12 +177,56 @@ class JSONConnection(Connection):
176177
API_BASE_URL = None
177178
"""The base of the API call URL."""
178179

180+
API_BASE_MTLS_URL = None
181+
"""The base of the API call URL for mutual TLS."""
182+
183+
ALLOW_AUTO_SWITCH_TO_MTLS_URL = False
184+
"""Indicates if auto switch to mTLS url is allowed."""
185+
179186
API_VERSION = None
180187
"""The version of the API, used in building the API call's URL."""
181188

182189
API_URL_TEMPLATE = None
183190
"""A template for the URL of a particular API call."""
184191

192+
def get_api_base_url_for_mtls(self, api_base_url=None):
193+
"""Return the api base url for mutual TLS.
194+
195+
Typically, you shouldn't need to use this method.
196+
197+
The logic is as follows:
198+
199+
If `api_base_url` is provided, just return this value; otherwise, the
200+
return value depends `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable
201+
value.
202+
203+
If the environment variable value is "always", return `API_BASE_MTLS_URL`.
204+
If the environment variable value is "never", return `API_BASE_URL`.
205+
Otherwise, if `ALLOW_AUTO_SWITCH_TO_MTLS_URL` is True and the underlying
206+
http is mTLS, then return `API_BASE_MTLS_URL`; otherwise return `API_BASE_URL`.
207+
208+
:type api_base_url: str
209+
:param api_base_url: User provided api base url. It takes precedence over
210+
`API_BASE_URL` and `API_BASE_MTLS_URL`.
211+
212+
:rtype: str
213+
:returns: The api base url used for mTLS.
214+
"""
215+
if api_base_url:
216+
return api_base_url
217+
218+
env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
219+
if env == "always":
220+
url_to_use = self.API_BASE_MTLS_URL
221+
elif env == "never":
222+
url_to_use = self.API_BASE_URL
223+
else:
224+
if self.ALLOW_AUTO_SWITCH_TO_MTLS_URL:
225+
url_to_use = self.API_BASE_MTLS_URL if self.http.is_mtls else self.API_BASE_URL
226+
else:
227+
url_to_use = self.API_BASE_URL
228+
return url_to_use
229+
185230
def build_api_url(
186231
self, path, query_params=None, api_base_url=None, api_version=None
187232
):
@@ -210,7 +255,7 @@ def build_api_url(
210255
:returns: The URL assembled from the pieces provided.
211256
"""
212257
url = self.API_URL_TEMPLATE.format(
213-
api_base_url=(api_base_url or self.API_BASE_URL),
258+
api_base_url=self.get_api_base_url_for_mtls(api_base_url),
214259
api_version=(api_version or self.API_VERSION),
215260
path=path,
216261
)

google/cloud/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ def __init__(self, credentials=None, _http=None, client_options=None):
159159
self._credentials = self._credentials.with_quota_project(client_options.quota_project_id)
160160

161161
self._http_internal = _http
162+
self._client_cert_source = client_options.client_cert_source
162163

163164
def __getstate__(self):
164165
"""Explicitly state that clients are not pickleable."""
@@ -183,6 +184,7 @@ def _http(self):
183184
self._credentials,
184185
refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT,
185186
)
187+
self._http_internal.configure_mtls_channel(self._client_cert_source)
186188
return self._http_internal
187189

188190

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
release_status = "Development Status :: 5 - Production/Stable"
3030
dependencies = [
3131
"google-api-core >= 1.21.0, < 2.0.0dev",
32+
"google-auth >= 1.24.0, < 2.0dev",
3233
# Support six==1.12.0 due to App Engine standard runtime.
3334
# https://github.com/googleapis/python-cloud-core/issues/45
3435
"six >=1.12.0",

tests/unit/test__http.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import json
16+
import os
1617
import unittest
1718
import warnings
1819

@@ -165,6 +166,7 @@ def _make_mock_one(self, *args, **kw):
165166
class MockConnection(self._get_target_class()):
166167
API_URL_TEMPLATE = "{api_base_url}/mock/{api_version}{path}"
167168
API_BASE_URL = "http://mock"
169+
API_BASE_MTLS_URL = "https://mock.mtls"
168170
API_VERSION = "vMOCK"
169171

170172
return MockConnection(*args, **kw)
@@ -230,6 +232,50 @@ def test_build_api_url_w_extra_query_params_tuples(self):
230232
self.assertEqual(parms["qux"], ["quux", "corge"])
231233
self.assertEqual(parms["prettyPrint"], ["false"])
232234

235+
def test_get_api_base_url_for_mtls_w_api_base_url(self):
236+
client = object()
237+
conn = self._make_mock_one(client)
238+
uri = conn.get_api_base_url_for_mtls(api_base_url="http://foo")
239+
self.assertEqual(uri, "http://foo")
240+
241+
def test_get_api_base_url_for_mtls_env_always(self):
242+
client = object()
243+
conn = self._make_mock_one(client)
244+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
245+
uri = conn.get_api_base_url_for_mtls()
246+
self.assertEqual(uri, "https://mock.mtls")
247+
248+
def test_get_api_base_url_for_mtls_env_never(self):
249+
client = object()
250+
conn = self._make_mock_one(client)
251+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
252+
uri = conn.get_api_base_url_for_mtls()
253+
self.assertEqual(uri, "http://mock")
254+
255+
def test_get_api_base_url_for_mtls_env_auto(self):
256+
client = mock.Mock()
257+
client._http = mock.Mock()
258+
client._http.is_mtls = False
259+
conn = self._make_mock_one(client)
260+
261+
# ALLOW_AUTO_SWITCH_TO_MTLS_URL is False, so use regular endpoint.
262+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}):
263+
uri = conn.get_api_base_url_for_mtls()
264+
self.assertEqual(uri, "http://mock")
265+
266+
# ALLOW_AUTO_SWITCH_TO_MTLS_URL is True, so now endpoint dependes
267+
# on client._http.is_mtls
268+
conn.ALLOW_AUTO_SWITCH_TO_MTLS_URL = True
269+
270+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}):
271+
uri = conn.get_api_base_url_for_mtls()
272+
self.assertEqual(uri, "http://mock")
273+
274+
client._http.is_mtls = True
275+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}):
276+
uri = conn.get_api_base_url_for_mtls()
277+
self.assertEqual(uri, "https://mock.mtls")
278+
233279
def test__make_request_no_data_no_content_type_no_headers(self):
234280
from google.cloud._http import CLIENT_INFO_HEADER
235281

tests/unit/test_client.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,22 @@ def test_ctor__http_property_new(self):
125125
from google.cloud.client import _CREDENTIALS_REFRESH_TIMEOUT
126126

127127
credentials = _make_credentials()
128-
client = self._make_one(credentials=credentials)
128+
mock_client_cert_source = mock.Mock()
129+
client_options = {'client_cert_source': mock_client_cert_source}
130+
client = self._make_one(credentials=credentials, client_options=client_options)
129131
self.assertIsNone(client._http_internal)
130132

131-
authorized_session_patch = mock.patch(
132-
"google.auth.transport.requests.AuthorizedSession",
133-
return_value=mock.sentinel.http,
134-
)
135-
with authorized_session_patch as AuthorizedSession:
136-
self.assertIs(client._http, mock.sentinel.http)
133+
with mock.patch('google.auth.transport.requests.AuthorizedSession') as AuthorizedSession:
134+
session = mock.Mock()
135+
session.configure_mtls_channel = mock.Mock()
136+
AuthorizedSession.return_value = session
137+
self.assertIs(client._http, session)
137138
# Check the mock.
138139
AuthorizedSession.assert_called_once_with(credentials, refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT)
140+
session.configure_mtls_channel.assert_called_once_with(mock_client_cert_source)
139141
# Make sure the cached value is used on subsequent access.
140-
self.assertIs(client._http_internal, mock.sentinel.http)
141-
self.assertIs(client._http, mock.sentinel.http)
142+
self.assertIs(client._http_internal, session)
143+
self.assertIs(client._http, session)
142144
self.assertEqual(AuthorizedSession.call_count, 1)
143145

144146
def test_from_service_account_json(self):

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://www.github.com/googleapis/python-cloud-core/commit/a93129bf2fa6fabda3de8b8a342f31a328ccbaf0

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy