Skip to content
This repository has been archived by the owner on Jul 6, 2023. It is now read-only.

feat: add always_use_jwt_access #10

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -25,6 +25,7 @@
from google.api_core import retry as retries # type: ignore
from google.api_core import operations_v1 # type: ignore
from google.auth import credentials as ga_credentials # type: ignore
from google.oauth2 import service_account # type: ignore

from google.cloud.service_usage_v1.types import resources
from google.cloud.service_usage_v1.types import serviceusage
Expand All @@ -48,8 +49,6 @@
except pkg_resources.DistributionNotFound: # pragma: NO COVER
_GOOGLE_AUTH_VERSION = None

_API_CORE_VERSION = google.api_core.__version__


class ServiceUsageTransport(abc.ABC):
"""Abstract transport class for ServiceUsage."""
Expand All @@ -71,6 +70,7 @@ def __init__(
scopes: Optional[Sequence[str]] = None,
quota_project_id: Optional[str] = None,
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
always_use_jwt_access: Optional[bool] = False,
**kwargs,
) -> None:
"""Instantiate the transport.
Expand All @@ -94,6 +94,8 @@ def __init__(
API requests. If ``None``, then default info will be used.
Generally, you only need to set this if you're developing
your own client library.
always_use_jwt_access (Optional[bool]): Whether self signed JWT should
be used for service account credentials.
"""
# Save the hostname. Default to port 443 (HTTPS) if none is specified.
if ":" not in host:
Expand Down Expand Up @@ -122,13 +124,20 @@ def __init__(
**scopes_kwargs, quota_project_id=quota_project_id
)

# If the credentials is service account credentials, then always try to use self signed JWT.
if (
always_use_jwt_access
and isinstance(credentials, service_account.Credentials)
and hasattr(service_account.Credentials, "with_always_use_jwt_access")
):
credentials = credentials.with_always_use_jwt_access(True)

# Save the credentials.
self._credentials = credentials

# TODO(busunkim): These two class methods are in the base transport
# TODO(busunkim): This method is in the base transport
# to avoid duplicating code across the transport classes. These functions
# should be deleted once the minimum required versions of google-api-core
# and google-auth are increased.
# should be deleted once the minimum required versions of google-auth is increased.

# TODO: Remove this function once google-auth >= 1.25.0 is required
@classmethod
Expand All @@ -149,27 +158,6 @@ def _get_scopes_kwargs(

return scopes_kwargs

# TODO: Remove this function once google-api-core >= 1.26.0 is required
@classmethod
def _get_self_signed_jwt_kwargs(
cls, host: str, scopes: Optional[Sequence[str]]
) -> Dict[str, Union[Optional[Sequence[str]], str]]:
"""Returns kwargs to pass to grpc_helpers.create_channel depending on the google-api-core version"""

self_signed_jwt_kwargs: Dict[str, Union[Optional[Sequence[str]], str]] = {}

if _API_CORE_VERSION and (
packaging.version.parse(_API_CORE_VERSION)
>= packaging.version.parse("1.26.0")
):
self_signed_jwt_kwargs["default_scopes"] = cls.AUTH_SCOPES
self_signed_jwt_kwargs["scopes"] = scopes
self_signed_jwt_kwargs["default_host"] = cls.DEFAULT_HOST
else:
self_signed_jwt_kwargs["scopes"] = scopes or cls.AUTH_SCOPES

return self_signed_jwt_kwargs

def _prep_wrapped_messages(self, client_info):
# Precompute the wrapped methods.
self._wrapped_methods = {
Expand Down
Expand Up @@ -158,6 +158,7 @@ def __init__(
scopes=scopes,
quota_project_id=quota_project_id,
client_info=client_info,
always_use_jwt_access=True,
)

if not self._grpc_channel:
Expand Down Expand Up @@ -213,14 +214,14 @@ def create_channel(
and ``credentials_file`` are passed.
"""

self_signed_jwt_kwargs = cls._get_self_signed_jwt_kwargs(host, scopes)

return grpc_helpers.create_channel(
host,
credentials=credentials,
credentials_file=credentials_file,
quota_project_id=quota_project_id,
**self_signed_jwt_kwargs,
default_scopes=cls.AUTH_SCOPES,
scopes=scopes,
default_host=cls.DEFAULT_HOST,
**kwargs,
)

Expand Down
Expand Up @@ -86,14 +86,14 @@ def create_channel(
aio.Channel: A gRPC AsyncIO channel object.
"""

self_signed_jwt_kwargs = cls._get_self_signed_jwt_kwargs(host, scopes)

return grpc_helpers_async.create_channel(
host,
credentials=credentials,
credentials_file=credentials_file,
quota_project_id=quota_project_id,
**self_signed_jwt_kwargs,
default_scopes=cls.AUTH_SCOPES,
scopes=scopes,
default_host=cls.DEFAULT_HOST,
**kwargs,
)

Expand Down Expand Up @@ -204,6 +204,7 @@ def __init__(
scopes=scopes,
quota_project_id=quota_project_id,
client_info=client_info,
always_use_jwt_access=True,
)

if not self._grpc_channel:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -26,7 +26,7 @@
release_status = "Development Status :: 4 - Beta"
url = "https://github.com/googleapis/python-service-usage"
dependencies = [
"google-api-core[grpc] >= 1.22.2, < 2.0.0dev",
"google-api-core[grpc] >= 1.26.0, <2.0.0dev",
"proto-plus >= 1.15.0",
"packaging >= 14.3",
]
Expand Down
2 changes: 1 addition & 1 deletion testing/constraints-3.6.txt
Expand Up @@ -4,7 +4,7 @@
# Pin the version to the lower bound.
# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0dev",
# Then this file should have google-cloud-foo==1.14.0
google-api-core==1.22.2
google-api-core==1.26.0
proto-plus==1.15.0
packaging==14.3
google-auth==1.24.0 # TODO: remove when google-auth>=1.25.0 si transitively required through google-api-core
106 changes: 13 additions & 93 deletions tests/unit/gapic/service_usage_v1/test_service_usage.py
Expand Up @@ -38,9 +38,6 @@
from google.cloud.service_usage_v1.services.service_usage import ServiceUsageClient
from google.cloud.service_usage_v1.services.service_usage import pagers
from google.cloud.service_usage_v1.services.service_usage import transports
from google.cloud.service_usage_v1.services.service_usage.transports.base import (
_API_CORE_VERSION,
)
from google.cloud.service_usage_v1.services.service_usage.transports.base import (
_GOOGLE_AUTH_VERSION,
)
Expand All @@ -51,8 +48,9 @@
import google.auth


# TODO(busunkim): Once google-api-core >= 1.26.0 is required:
# - Delete all the api-core and auth "less than" test cases
# TODO(busunkim): Once google-auth >= 1.25.0 is required transitively
# through google-api-core:
# - Delete the auth "less than" test cases
# - Delete these pytest markers (Make the "greater than or equal to" tests the default).
requires_google_auth_lt_1_25_0 = pytest.mark.skipif(
packaging.version.parse(_GOOGLE_AUTH_VERSION) >= packaging.version.parse("1.25.0"),
Expand All @@ -63,16 +61,6 @@
reason="This test requires google-auth >= 1.25.0",
)

requires_api_core_lt_1_26_0 = pytest.mark.skipif(
packaging.version.parse(_API_CORE_VERSION) >= packaging.version.parse("1.26.0"),
reason="This test requires google-api-core < 1.26.0",
)

requires_api_core_gte_1_26_0 = pytest.mark.skipif(
packaging.version.parse(_API_CORE_VERSION) < packaging.version.parse("1.26.0"),
reason="This test requires google-api-core >= 1.26.0",
)


def client_cert_source_callback():
return b"cert bytes", b"key bytes"
Expand Down Expand Up @@ -130,6 +118,16 @@ def test_service_usage_client_from_service_account_info(client_class):
assert client.transport._host == "serviceusage.googleapis.com:443"


@pytest.mark.parametrize("client_class", [ServiceUsageClient, ServiceUsageAsyncClient,])
def test_service_usage_client_service_account_always_use_jwt(client_class):
with mock.patch.object(
service_account.Credentials, "with_always_use_jwt_access", create=True
) as use_jwt:
creds = service_account.Credentials(None, None, None)
client = client_class(credentials=creds)
use_jwt.assert_called_with(True)


@pytest.mark.parametrize("client_class", [ServiceUsageClient, ServiceUsageAsyncClient,])
def test_service_usage_client_from_service_account_file(client_class):
creds = ga_credentials.AnonymousCredentials()
Expand Down Expand Up @@ -1679,7 +1677,6 @@ def test_service_usage_transport_auth_adc_old_google_auth(transport_class):
(transports.ServiceUsageGrpcAsyncIOTransport, grpc_helpers_async),
],
)
@requires_api_core_gte_1_26_0
def test_service_usage_transport_create_channel(transport_class, grpc_helpers):
# If credentials and host are not provided, the transport class should use
# ADC credentials.
Expand Down Expand Up @@ -1712,83 +1709,6 @@ def test_service_usage_transport_create_channel(transport_class, grpc_helpers):
)


@pytest.mark.parametrize(
"transport_class,grpc_helpers",
[
(transports.ServiceUsageGrpcTransport, grpc_helpers),
(transports.ServiceUsageGrpcAsyncIOTransport, grpc_helpers_async),
],
)
@requires_api_core_lt_1_26_0
def test_service_usage_transport_create_channel_old_api_core(
transport_class, grpc_helpers
):
# If credentials and host are not provided, the transport class should use
# ADC credentials.
with mock.patch.object(
google.auth, "default", autospec=True
) as adc, mock.patch.object(
grpc_helpers, "create_channel", autospec=True
) as create_channel:
creds = ga_credentials.AnonymousCredentials()
adc.return_value = (creds, None)
transport_class(quota_project_id="octopus")

create_channel.assert_called_with(
"serviceusage.googleapis.com:443",
credentials=creds,
credentials_file=None,
quota_project_id="octopus",
scopes=(
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/cloud-platform.read-only",
"https://www.googleapis.com/auth/service.management",
),
ssl_credentials=None,
options=[
("grpc.max_send_message_length", -1),
("grpc.max_receive_message_length", -1),
],
)


@pytest.mark.parametrize(
"transport_class,grpc_helpers",
[
(transports.ServiceUsageGrpcTransport, grpc_helpers),
(transports.ServiceUsageGrpcAsyncIOTransport, grpc_helpers_async),
],
)
@requires_api_core_lt_1_26_0
def test_service_usage_transport_create_channel_user_scopes(
transport_class, grpc_helpers
):
# If credentials and host are not provided, the transport class should use
# ADC credentials.
with mock.patch.object(
google.auth, "default", autospec=True
) as adc, mock.patch.object(
grpc_helpers, "create_channel", autospec=True
) as create_channel:
creds = ga_credentials.AnonymousCredentials()
adc.return_value = (creds, None)

transport_class(quota_project_id="octopus", scopes=["1", "2"])

create_channel.assert_called_with(
"serviceusage.googleapis.com:443",
credentials=creds,
credentials_file=None,
quota_project_id="octopus",
scopes=["1", "2"],
ssl_credentials=None,
options=[
("grpc.max_send_message_length", -1),
("grpc.max_receive_message_length", -1),
],
)


@pytest.mark.parametrize(
"transport_class",
[transports.ServiceUsageGrpcTransport, transports.ServiceUsageGrpcAsyncIOTransport],
Expand Down