Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: allow default_host and default_scopes to be passed to create_ch…
…annel (#134)

Add `default_host` and `default_scopes` parameters to `create_channel` so self-signed JWTs can be used.
  • Loading branch information
busunkim96 committed Feb 5, 2021
1 parent db48ea3 commit 94c76e0
Show file tree
Hide file tree
Showing 6 changed files with 429 additions and 26 deletions.
77 changes: 70 additions & 7 deletions google/api_core/grpc_helpers.py
Expand Up @@ -17,6 +17,8 @@
import collections

import grpc
from packaging import version
import pkg_resources
import six

from google.api_core import exceptions
Expand All @@ -33,6 +35,20 @@
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)

Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand All @@ -210,21 +232,55 @@ 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)

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)
Expand All @@ -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.
Expand All @@ -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`.
Expand All @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions google/api_core/grpc_helpers_async.py
Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -34,6 +34,7 @@
"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"',
Expand Down
1 change: 1 addition & 0 deletions testing/constraints-3.6.txt
Expand Up @@ -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
Expand Down

0 comments on commit 94c76e0

Please sign in to comment.