Repository: googleapis/python-api-core
Pull request #134 (2021-01-20 00:34:54)

feat: allow default_host and default_scopes to be passed to create_channel
Add `default_host` and `default_scopes` parameters to `create_channel` so self-signed JWTs can be used. See https://github.com/googleapis/google-auth-library-python/pull/665 for the change in the auth library and https://github.com/googleapis/python-translate/pull/107 for the changes in the GAPIC library.


busunkim96: Reviewers, PTAL. Thank you!

Patch:
diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py
index 0ccbe12..5937f18 100644
--- a/google/api_core/grpc_helpers.py
+++ b/google/api_core/grpc_helpers.py
@@ -17,6 +17,8 @@
 import collections
 
 import grpc
+from packaging import version
+import pkg_resources
 import six
 
 from google.api_core import exceptions
@@ -33,6 +35,20 @@ try:
 except ImportError:
     HAS_GRPC_GCP = False
 
+try:
+    # google.auth.__version__ was added in 1.26.0
+    _GOOGLE_AUTH_VERSION = google.auth.__version__
+except AttributeError:
+    try:  # try pkg_resources if it is available
+        _GOOGLE_AUTH_VERSION = pkg_resources.get_distribution("google-auth").version
+    except pkg_resources.DistributionNotFound:  # pragma: NO COVER
+        _GOOGLE_AUTH_VERSION = None
+
+if _GOOGLE_AUTH_VERSION is not None and version.parse(_GOOGLE_AUTH_VERSION) >= version.parse("1.25.0"):
+    _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST = True
+else:
+    _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST = False
+
 # The list of gRPC Callable interfaces that return iterators.
 _STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)
 
@@ -179,9 +195,11 @@ def wrap_errors(callable_):
 def _create_composite_credentials(
         credentials=None,
         credentials_file=None,
+        default_scopes=None,
         scopes=None,
         ssl_credentials=None,
-        quota_project_id=None):
+        quota_project_id=None,
+        default_host=None):
     """Create the composite credentials for secure channels.
 
     Args:
@@ -191,12 +209,16 @@ def _create_composite_credentials(
         credentials_file (str): A file with credentials that can be loaded with
             :func:`google.auth.load_credentials_from_file`. This argument is
             mutually exclusive with credentials.
+        default_scopes (Sequence[str]): A optional list of scopes needed for this
+            service. These are only used when credentials are not specified and
+            are passed to :func:`google.auth.default`.
         scopes (Sequence[str]): A optional list of scopes needed for this
             service. These are only used when credentials are not specified and
             are passed to :func:`google.auth.default`.
         ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
             credentials. This can be used to specify different certificates.
         quota_project_id (str): An optional project to use for billing and quota.
+        default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
 
     Returns:
         grpc.ChannelCredentials: The composed channel credentials object.
@@ -210,11 +232,38 @@ def _create_composite_credentials(
         )
 
     if credentials_file:
-        credentials, _ = google.auth.load_credentials_from_file(credentials_file, scopes=scopes)
+        # TODO: remove this if/else once google-auth >= 1.25.0 is required
+        if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+            credentials, _ = google.auth.load_credentials_from_file(
+                credentials_file,
+                scopes=scopes,
+                default_scopes=default_scopes
+            )
+        else:
+            credentials, _ = google.auth.load_credentials_from_file(
+                credentials_file,
+                scopes=scopes or default_scopes,
+            )
     elif credentials:
-        credentials = google.auth.credentials.with_scopes_if_required(credentials, scopes)
+        # TODO: remove this if/else once google-auth >= 1.25.0 is required
+        if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+            credentials = google.auth.credentials.with_scopes_if_required(
+                credentials,
+                scopes=scopes,
+                default_scopes=default_scopes
+            )
+        else:
+            credentials = google.auth.credentials.with_scopes_if_required(
+                credentials,
+                scopes=scopes or default_scopes,
+            )
+
     else:
-        credentials, _ = google.auth.default(scopes=scopes)
+        # TODO: remove this if/else once google-auth >= 1.25.0 is required
+        if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+            credentials, _ = google.auth.default(scopes=scopes, default_scopes=default_scopes)
+        else:
+            credentials, _ = google.auth.default(scopes=scopes or default_scopes)
 
     if quota_project_id and isinstance(credentials, google.auth.credentials.CredentialsWithQuotaProject):
         credentials = credentials.with_quota_project(quota_project_id)
@@ -222,9 +271,16 @@ def _create_composite_credentials(
     request = google.auth.transport.requests.Request()
 
     # Create the metadata plugin for inserting the authorization header.
-    metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
-        credentials, request
-    )
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
+            credentials, request, default_host=default_host,
+        )
+    else:
+        metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
+            credentials, request
+        )
 
     # Create a set of grpc.CallCredentials using the metadata plugin.
     google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
@@ -245,6 +301,8 @@ def create_channel(
         ssl_credentials=None,
         credentials_file=None,
         quota_project_id=None,
+        default_scopes=None,
+        default_host=None,
         **kwargs):
     """Create a secure channel with credentials.
 
@@ -262,6 +320,9 @@ def create_channel(
             :func:`google.auth.load_credentials_from_file`. This argument is
             mutually exclusive with credentials.
         quota_project_id (str): An optional project to use for billing and quota.
+        default_scopes (Sequence[str]): Default scopes passed by a Google client
+            library. Use 'scopes' for user-defined scopes.
+        default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
         kwargs: Additional key-word args passed to
             :func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
 
@@ -275,9 +336,11 @@ def create_channel(
     composite_credentials = _create_composite_credentials(
         credentials=credentials,
         credentials_file=credentials_file,
+        default_scopes=default_scopes,
         scopes=scopes,
         ssl_credentials=ssl_credentials,
         quota_project_id=quota_project_id,
+        default_host=default_host,
     )
 
     if HAS_GRPC_GCP:
diff --git a/google/api_core/grpc_helpers_async.py b/google/api_core/grpc_helpers_async.py
index 9a994e9..14eb5a1 100644
--- a/google/api_core/grpc_helpers_async.py
+++ b/google/api_core/grpc_helpers_async.py
@@ -213,6 +213,8 @@ def create_channel(
         ssl_credentials=None,
         credentials_file=None,
         quota_project_id=None,
+        default_scopes=None,
+        default_host=None,
         **kwargs):
     """Create an AsyncIO secure channel with credentials.
 
@@ -230,6 +232,9 @@ def create_channel(
             :func:`google.auth.load_credentials_from_file`. This argument is
             mutually exclusive with credentials.
         quota_project_id (str): An optional project to use for billing and quota.
+        default_scopes (Sequence[str]): Default scopes passed by a Google client
+            library. Use 'scopes' for user-defined scopes.
+        default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
         kwargs: Additional key-word args passed to :func:`aio.secure_channel`.
 
     Returns:
@@ -243,8 +248,10 @@ def create_channel(
         credentials=credentials,
         credentials_file=credentials_file,
         scopes=scopes,
+        default_scopes=default_scopes,
         ssl_credentials=ssl_credentials,
         quota_project_id=quota_project_id,
+        default_host=default_host
     )
 
     return aio.secure_channel(target, composite_credentials, **kwargs)
diff --git a/setup.py b/setup.py
index 30adb95..5de5aaf 100644
--- a/setup.py
+++ b/setup.py
@@ -34,6 +34,7 @@ dependencies = [
     "google-auth >= 1.21.1, < 2.0dev",
     "requests >= 2.18.0, < 3.0.0dev",
     "setuptools >= 40.3.0",
+    "packaging >= 14.3",
     "six >= 1.13.0",
     "pytz",
     'futures >= 3.2.0; python_version < "3.2"',


Test patch:
diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt
index 2d49817..1fcd193 100644
--- a/testing/constraints-3.6.txt
+++ b/testing/constraints-3.6.txt
@@ -10,6 +10,7 @@ protobuf==3.12.0
 google-auth==1.21.1
 requests==2.18.0
 setuptools==40.3.0
+packaging==14.3
 six==1.13.0
 grpcio==1.29.0
 grpcio-gcp==0.2.2
diff --git a/tests/asyncio/test_grpc_helpers_async.py b/tests/asyncio/test_grpc_helpers_async.py
index 766e11a..3461cbe 100644
--- a/tests/asyncio/test_grpc_helpers_async.py
+++ b/tests/asyncio/test_grpc_helpers_async.py
@@ -18,6 +18,7 @@ import mock
 import pytest
 
 from google.api_core import exceptions
+from google.api_core import grpc_helpers
 from google.api_core import grpc_helpers_async
 import google.auth.credentials
 
@@ -263,6 +264,7 @@ def test_wrap_errors_streaming(wrap_stream_errors):
 @mock.patch("grpc.composite_channel_credentials")
 @mock.patch(
     "google.auth.default",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.projet),
 )
 @mock.patch("grpc.experimental.aio.secure_channel")
@@ -273,7 +275,45 @@ def test_create_channel_implicit(grpc_secure_channel, default, composite_creds_c
     channel = grpc_helpers_async.create_channel(target)
 
     assert channel is grpc_secure_channel.return_value
-    default.assert_called_once_with(scopes=None)
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=None)
+    else:
+        default.assert_called_once_with(scopes=None)
+    grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("google.auth.transport.grpc.AuthMetadataPlugin", autospec=True)
+@mock.patch(
+    "google.auth.transport.requests.Request",
+    autospec=True,
+    return_value=mock.sentinel.Request
+)
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch(
+    "google.auth.default",
+    autospec=True,
+    return_value=(mock.sentinel.credentials, mock.sentinel.projet),
+)
+@mock.patch("grpc.experimental.aio.secure_channel")
+def test_create_channel_implicit_with_default_host(grpc_secure_channel, default, composite_creds_call, request, auth_metadata_plugin):
+    target = "example.com:443"
+    default_host = "example.com"
+    composite_creds = composite_creds_call.return_value
+
+    channel = grpc_helpers_async.create_channel(target, default_host=default_host)
+
+    assert channel is grpc_secure_channel.return_value
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=None)
+        auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request, default_host=default_host)
+    else:
+        default.assert_called_once_with(scopes=None)
+        auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request)
+
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
 
 
@@ -292,7 +332,12 @@ def test_create_channel_implicit_with_ssl_creds(
 
     grpc_helpers_async.create_channel(target, ssl_credentials=ssl_creds)
 
-    default.assert_called_once_with(scopes=None)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=None)
+    else:
+        default.assert_called_once_with(scopes=None)
+
     composite_creds_call.assert_called_once_with(ssl_creds, mock.ANY)
     composite_creds = composite_creds_call.return_value
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
@@ -301,6 +346,7 @@ def test_create_channel_implicit_with_ssl_creds(
 @mock.patch("grpc.composite_channel_credentials")
 @mock.patch(
     "google.auth.default",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.projet),
 )
 @mock.patch("grpc.experimental.aio.secure_channel")
@@ -313,7 +359,39 @@ def test_create_channel_implicit_with_scopes(
     channel = grpc_helpers_async.create_channel(target, scopes=["one", "two"])
 
     assert channel is grpc_secure_channel.return_value
-    default.assert_called_once_with(scopes=["one", "two"])
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=["one", "two"], default_scopes=None)
+    else:
+        default.assert_called_once_with(scopes=["one", "two"])
+
+    grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch(
+    "google.auth.default",
+    autospec=True,
+    return_value=(mock.sentinel.credentials, mock.sentinel.projet),
+)
+@mock.patch("grpc.experimental.aio.secure_channel")
+def test_create_channel_implicit_with_default_scopes(
+    grpc_secure_channel, default, composite_creds_call
+):
+    target = "example.com:443"
+    composite_creds = composite_creds_call.return_value
+
+    channel = grpc_helpers_async.create_channel(target, default_scopes=["three", "four"])
+
+    assert channel is grpc_secure_channel.return_value
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=["three", "four"])
+    else:
+        default.assert_called_once_with(scopes=["three", "four"])
+
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
 
 
@@ -331,7 +409,7 @@ def test_create_channel_explicit_with_duplicate_credentials():
 
 
 @mock.patch("grpc.composite_channel_credentials")
-@mock.patch("google.auth.credentials.with_scopes_if_required")
+@mock.patch("google.auth.credentials.with_scopes_if_required", autospec=True)
 @mock.patch("grpc.experimental.aio.secure_channel")
 def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_creds_call):
     target = "example.com:443"
@@ -339,7 +417,12 @@ def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_cred
 
     channel = grpc_helpers_async.create_channel(target, credentials=mock.sentinel.credentials)
 
-    auth_creds.assert_called_once_with(mock.sentinel.credentials, None)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None, default_scopes=None)
+    else:
+        auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None)
+
     assert channel is grpc_secure_channel.return_value
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
 
@@ -357,8 +440,36 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal
     channel = grpc_helpers_async.create_channel(
         target, credentials=credentials, scopes=scopes
     )
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes)
+
+    assert channel is grpc_secure_channel.return_value
+    grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch("grpc.experimental.aio.secure_channel")
+def test_create_channel_explicit_default_scopes(grpc_secure_channel, composite_creds_call):
+    target = "example.com:443"
+    default_scopes = ["3", "4"]
+    composite_creds = composite_creds_call.return_value
+
+    credentials = mock.create_autospec(google.auth.credentials.Scoped, instance=True)
+    credentials.requires_scopes = True
+
+    channel = grpc_helpers_async.create_channel(
+        target, credentials=credentials, default_scopes=default_scopes
+    )
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes=None, default_scopes=default_scopes)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes=default_scopes)
 
-    credentials.with_scopes.assert_called_once_with(scopes)
     assert channel is grpc_secure_channel.return_value
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
 
@@ -384,6 +495,7 @@ def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composi
 @mock.patch("grpc.experimental.aio.secure_channel")
 @mock.patch(
     "google.auth.load_credentials_from_file",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.project)
 )
 def test_create_channnel_with_credentials_file(load_credentials_from_file, grpc_secure_channel, composite_creds_call):
@@ -396,7 +508,12 @@ def test_create_channnel_with_credentials_file(load_credentials_from_file, grpc_
         target, credentials_file=credentials_file
     )
 
-    google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=None)
+    else:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None)
+
     assert channel is grpc_secure_channel.return_value
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
 
@@ -405,6 +522,7 @@ def test_create_channnel_with_credentials_file(load_credentials_from_file, grpc_
 @mock.patch("grpc.experimental.aio.secure_channel")
 @mock.patch(
     "google.auth.load_credentials_from_file",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.project)
 )
 def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call):
@@ -418,7 +536,40 @@ def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_f
         target, credentials_file=credentials_file, scopes=scopes
     )
 
-    google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes, default_scopes=None)
+    else:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes)
+
+    assert channel is grpc_secure_channel.return_value
+    grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch("grpc.experimental.aio.secure_channel")
+@mock.patch(
+    "google.auth.load_credentials_from_file",
+    autospec=True,
+    return_value=(mock.sentinel.credentials, mock.sentinel.project)
+)
+def test_create_channel_with_credentials_file_and_default_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call):
+    target = "example.com:443"
+    default_scopes = ["3", "4"]
+
+    credentials_file = "/path/to/credentials/file.json"
+    composite_creds = composite_creds_call.return_value
+
+    channel = grpc_helpers_async.create_channel(
+        target, credentials_file=credentials_file, default_scopes=default_scopes
+    )
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=default_scopes)
+    else:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=default_scopes)
+
     assert channel is grpc_secure_channel.return_value
     grpc_secure_channel.assert_called_once_with(target, composite_creds)
 
@@ -434,7 +585,12 @@ def test_create_channel_without_grpc_gcp(grpc_secure_channel):
 
     grpc_helpers_async.create_channel(target, credentials=credentials, scopes=scopes)
     grpc_secure_channel.assert_called()
-    credentials.with_scopes.assert_called_once_with(scopes)
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes)
 
 
 @pytest.mark.asyncio
diff --git a/tests/unit/test_grpc_helpers.py b/tests/unit/test_grpc_helpers.py
index d6ec60a..4e0ab80 100644
--- a/tests/unit/test_grpc_helpers.py
+++ b/tests/unit/test_grpc_helpers.py
@@ -222,6 +222,7 @@ def test_wrap_errors_streaming(wrap_stream_errors):
 @mock.patch("grpc.composite_channel_credentials")
 @mock.patch(
     "google.auth.default",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.projet),
 )
 @mock.patch("grpc.secure_channel")
@@ -232,7 +233,51 @@ def test_create_channel_implicit(grpc_secure_channel, default, composite_creds_c
     channel = grpc_helpers.create_channel(target)
 
     assert channel is grpc_secure_channel.return_value
-    default.assert_called_once_with(scopes=None)
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=None)
+    else:
+        default.assert_called_once_with(scopes=None)
+
+    if grpc_helpers.HAS_GRPC_GCP:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
+    else:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("google.auth.transport.grpc.AuthMetadataPlugin", autospec=True)
+@mock.patch(
+    "google.auth.transport.requests.Request",
+    autospec=True,
+    return_value=mock.sentinel.Request
+)
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch(
+    "google.auth.default",
+    autospec=True,
+    return_value=(mock.sentinel.credentials, mock.sentinel.project),
+)
+@mock.patch("grpc.secure_channel")
+def test_create_channel_implicit_with_default_host(grpc_secure_channel, default, composite_creds_call, request, auth_metadata_plugin):
+    target = "example.com:443"
+    default_host = "example.com"
+    composite_creds = composite_creds_call.return_value
+
+    channel = grpc_helpers.create_channel(target, default_host=default_host)
+
+    assert channel is grpc_secure_channel.return_value
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    print(grpc_helpers._GOOGLE_AUTH_VERSION)
+    print(grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST)
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=None)
+        auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request, default_host=default_host)
+    else:
+        default.assert_called_once_with(scopes=None)
+        auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request)
+
     if grpc_helpers.HAS_GRPC_GCP:
         grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
     else:
@@ -242,6 +287,7 @@ def test_create_channel_implicit(grpc_secure_channel, default, composite_creds_c
 @mock.patch("grpc.composite_channel_credentials")
 @mock.patch(
     "google.auth.default",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.projet),
 )
 @mock.patch("grpc.secure_channel")
@@ -254,7 +300,12 @@ def test_create_channel_implicit_with_ssl_creds(
 
     grpc_helpers.create_channel(target, ssl_credentials=ssl_creds)
 
-    default.assert_called_once_with(scopes=None)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=None)
+    else:
+        default.assert_called_once_with(scopes=None)
+
     composite_creds_call.assert_called_once_with(ssl_creds, mock.ANY)
     composite_creds = composite_creds_call.return_value
     if grpc_helpers.HAS_GRPC_GCP:
@@ -266,6 +317,7 @@ def test_create_channel_implicit_with_ssl_creds(
 @mock.patch("grpc.composite_channel_credentials")
 @mock.patch(
     "google.auth.default",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.projet),
 )
 @mock.patch("grpc.secure_channel")
@@ -278,7 +330,42 @@ def test_create_channel_implicit_with_scopes(
     channel = grpc_helpers.create_channel(target, scopes=["one", "two"])
 
     assert channel is grpc_secure_channel.return_value
-    default.assert_called_once_with(scopes=["one", "two"])
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=["one", "two"], default_scopes=None)
+    else:
+        default.assert_called_once_with(scopes=["one", "two"])
+
+    if grpc_helpers.HAS_GRPC_GCP:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
+    else:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch(
+    "google.auth.default",
+    autospec=True,
+    return_value=(mock.sentinel.credentials, mock.sentinel.projet),
+)
+@mock.patch("grpc.secure_channel")
+def test_create_channel_implicit_with_default_scopes(
+    grpc_secure_channel, default, composite_creds_call
+):
+    target = "example.com:443"
+    composite_creds = composite_creds_call.return_value
+
+    channel = grpc_helpers.create_channel(target, default_scopes=["three", "four"])
+
+    assert channel is grpc_secure_channel.return_value
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        default.assert_called_once_with(scopes=None, default_scopes=["three", "four"])
+    else:
+        default.assert_called_once_with(scopes=["three", "four"])
+
     if grpc_helpers.HAS_GRPC_GCP:
         grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
     else:
@@ -297,7 +384,7 @@ def test_create_channel_explicit_with_duplicate_credentials():
 
 
 @mock.patch("grpc.composite_channel_credentials")
-@mock.patch("google.auth.credentials.with_scopes_if_required")
+@mock.patch("google.auth.credentials.with_scopes_if_required", autospec=True)
 @mock.patch("grpc.secure_channel")
 def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_creds_call):
     target = "example.com:443"
@@ -305,7 +392,11 @@ def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_cred
 
     channel = grpc_helpers.create_channel(target, credentials=mock.sentinel.credentials)
 
-    auth_creds.assert_called_once_with(mock.sentinel.credentials, None)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None, default_scopes=None)
+    else:
+        auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None)
     assert channel is grpc_secure_channel.return_value
     if grpc_helpers.HAS_GRPC_GCP:
         grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
@@ -327,7 +418,39 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal
         target, credentials=credentials, scopes=scopes
     )
 
-    credentials.with_scopes.assert_called_once_with(scopes)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes)
+
+    assert channel is grpc_secure_channel.return_value
+    if grpc_helpers.HAS_GRPC_GCP:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
+    else:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch("grpc.secure_channel")
+def test_create_channel_explicit_default_scopes(grpc_secure_channel, composite_creds_call):
+    target = "example.com:443"
+    default_scopes = ["3", "4"]
+    composite_creds = composite_creds_call.return_value
+
+    credentials = mock.create_autospec(google.auth.credentials.Scoped, instance=True)
+    credentials.requires_scopes = True
+
+    channel = grpc_helpers.create_channel(
+        target, credentials=credentials, default_scopes=default_scopes
+    )
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes=None, default_scopes=default_scopes)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes=default_scopes)
+
     assert channel is grpc_secure_channel.return_value
     if grpc_helpers.HAS_GRPC_GCP:
         grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
@@ -362,6 +485,7 @@ def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composi
 @mock.patch("grpc.secure_channel")
 @mock.patch(
     "google.auth.load_credentials_from_file",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.project)
 )
 def test_create_channel_with_credentials_file(load_credentials_from_file, grpc_secure_channel, composite_creds_call):
@@ -374,7 +498,11 @@ def test_create_channel_with_credentials_file(load_credentials_from_file, grpc_s
         target, credentials_file=credentials_file
     )
 
-    google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=None)
+    else:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None)
 
     assert channel is grpc_secure_channel.return_value
     if grpc_helpers.HAS_GRPC_GCP:
@@ -387,6 +515,7 @@ def test_create_channel_with_credentials_file(load_credentials_from_file, grpc_s
 @mock.patch("grpc.secure_channel")
 @mock.patch(
     "google.auth.load_credentials_from_file",
+    autospec=True,
     return_value=(mock.sentinel.credentials, mock.sentinel.project)
 )
 def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call):
@@ -400,7 +529,43 @@ def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_f
         target, credentials_file=credentials_file, scopes=scopes
     )
 
-    google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes)
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes, default_scopes=None)
+    else:
+        google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes)
+
+    assert channel is grpc_secure_channel.return_value
+    if grpc_helpers.HAS_GRPC_GCP:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
+    else:
+        grpc_secure_channel.assert_called_once_with(target, composite_creds)
+
+
+@mock.patch("grpc.composite_channel_credentials")
+@mock.patch("grpc.secure_channel")
+@mock.patch(
+    "google.auth.load_credentials_from_file",
+    autospec=True,
+    return_value=(mock.sentinel.credentials, mock.sentinel.project)
+)
+def test_create_channel_with_credentials_file_and_default_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call):
+    target = "example.com:443"
+    default_scopes = ["3", "4"]
+
+    credentials_file = "/path/to/credentials/file.json"
+    composite_creds = composite_creds_call.return_value
+
+    channel = grpc_helpers.create_channel(
+        target, credentials_file=credentials_file, default_scopes=default_scopes
+    )
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=default_scopes)
+    else:
+        load_credentials_from_file.assert_called_once_with(credentials_file, scopes=default_scopes)
+
     assert channel is grpc_secure_channel.return_value
     if grpc_helpers.HAS_GRPC_GCP:
         grpc_secure_channel.assert_called_once_with(target, composite_creds, None)
@@ -421,7 +586,12 @@ def test_create_channel_with_grpc_gcp(grpc_gcp_secure_channel):
 
     grpc_helpers.create_channel(target, credentials=credentials, scopes=scopes)
     grpc_gcp_secure_channel.assert_called()
-    credentials.with_scopes.assert_called_once_with(scopes)
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes)
 
 
 @pytest.mark.skipif(grpc_helpers.HAS_GRPC_GCP, reason="grpc_gcp module not available")
@@ -435,7 +605,12 @@ def test_create_channel_without_grpc_gcp(grpc_secure_channel):
 
     grpc_helpers.create_channel(target, credentials=credentials, scopes=scopes)
     grpc_secure_channel.assert_called()
-    credentials.with_scopes.assert_called_once_with(scopes)
+
+    # TODO: remove this if/else once google-auth >= 1.25.0 is required
+    if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST:
+        credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None)
+    else:
+        credentials.with_scopes.assert_called_once_with(scopes)
 
 
 class TestChannelStub(object):