Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions google/api_core/client_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def get_client_cert():
"""

from typing import Callable, Mapping, Optional, Sequence, Tuple
import warnings

from google.api_core import general_helpers


class ClientOptions(object):
Expand All @@ -67,8 +70,9 @@ class ClientOptions(object):
and ``client_encrypted_cert_source`` are mutually exclusive.
quota_project_id (Optional[str]): A project name that a client's
quota belongs to.
credentials_file (Optional[str]): A path to a file storing credentials.
``credentials_file` and ``api_key`` are mutually exclusive.
credentials_file (Optional[str]): Deprecated. A path to a file storing credentials.
``credentials_file` and ``api_key`` are mutually exclusive. This argument will be
removed in the next major version of `google-api-core`.

.. warning::
Important: If you accept a credential configuration (credential JSON/File/Stream)
Expand Down Expand Up @@ -114,6 +118,9 @@ def __init__(
api_audience: Optional[str] = None,
universe_domain: Optional[str] = None,
):
if credentials_file is not None:
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

if client_cert_source and client_encrypted_cert_source:
raise ValueError(
"client_cert_source and client_encrypted_cert_source are mutually exclusive"
Expand Down
36 changes: 36 additions & 0 deletions google/api_core/general_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,39 @@

# This import for backward compatibility only.
from functools import wraps # noqa: F401 pragma: NO COVER

_CREDENTIALS_FILE_WARNING = """\
The `credentials_file` argument is deprecated because of a potential security risk.

The `google.auth.load_credentials_from_file` method does not validate the credential
configuration. The security risk occurs when a credential configuration is accepted
from a source that is not under your control and used without validation on your side.

If you know that you will be loading credential configurations of a
specific type, it is recommended to use a credential-type-specific
load method.

This will ensure that an unexpected credential type with potential for
malicious intent is not loaded unintentionally. You might still have to do
validation for certain credential types. Please follow the recommendations
for that method. For example, if you want to load only service accounts,
you can create the service account credentials explicitly:

```
from google.cloud.vision_v1 import ImageAnnotatorClient
from google.oauth2 import service_account

credentials = service_account.Credentials.from_service_account_file(filename)
client = ImageAnnotatorClient(credentials=credentials)
```

If you are loading your credential configuration from an untrusted source and have
not mitigated the risks (e.g. by validating the configuration yourself), make
these changes as soon as possible to prevent security risks to your environment.

Regardless of the method used, it is always your responsibility to validate
configurations received from external sources.

Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
for more details.
"""
17 changes: 10 additions & 7 deletions google/api_core/grpc_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,19 @@
# limitations under the License.

"""Helpers for :mod:`grpc`."""
from typing import Generic, Iterator, Optional, TypeVar

import collections
import functools
from typing import Generic, Iterator, Optional, TypeVar
import warnings

import grpc

from google.api_core import exceptions
import google.auth
import google.auth.credentials
import google.auth.transport.grpc
import google.auth.transport.requests
import google.protobuf
import grpc

from google.api_core import exceptions, general_helpers

PROTOBUF_VERSION = google.protobuf.__version__

Expand Down Expand Up @@ -213,9 +212,10 @@ def _create_composite_credentials(
credentials (google.auth.credentials.Credentials): The credentials. If
not specified, then this function will attempt to ascertain the
credentials from the environment using :func:`google.auth.default`.
credentials_file (str): A file with credentials that can be loaded with
credentials_file (str): Deprecated. A file with credentials that can be loaded with
:func:`google.auth.load_credentials_from_file`. This argument is
mutually exclusive with credentials.
mutually exclusive with credentials. This argument will be
removed in the next major version of `google-api-core`.

.. warning::
Important: If you accept a credential configuration (credential JSON/File/Stream)
Expand Down Expand Up @@ -245,6 +245,9 @@ def _create_composite_credentials(
Raises:
google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
"""
if credentials_file is not None:
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

if credentials and credentials_file:
raise exceptions.DuplicateCredentialArgs(
"'credentials' and 'credentials_file' are mutually exclusive."
Expand Down
11 changes: 8 additions & 3 deletions google/api_core/grpc_helpers_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@

import asyncio
import functools
import warnings

from typing import AsyncGenerator, Generic, Iterator, Optional, TypeVar

import grpc
from grpc import aio

from google.api_core import exceptions, grpc_helpers
from google.api_core import exceptions, general_helpers, grpc_helpers

# denotes the proto response type for grpc calls
P = TypeVar("P")
Expand Down Expand Up @@ -233,9 +234,10 @@ def create_channel(
are passed to :func:`google.auth.default`.
ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
credentials. This can be used to specify different certificates.
credentials_file (str): A file with credentials that can be loaded with
credentials_file (str): Deprecated. A file with credentials that can be loaded with
:func:`google.auth.load_credentials_from_file`. This argument is
mutually exclusive with credentials.
mutually exclusive with credentials. This argument will be
removed in the next major version of `google-api-core`.

.. warning::
Important: If you accept a credential configuration (credential JSON/File/Stream)
Expand Down Expand Up @@ -280,6 +282,9 @@ def create_channel(
ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
"""

if credentials_file is not None:
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

# If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
# raise ValueError as this is not yet supported.
# See https://github.com/googleapis/python-api-core/issues/590
Expand Down
20 changes: 13 additions & 7 deletions google/api_core/operations_v1/transports/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@
import abc
import re
from typing import Awaitable, Callable, Optional, Sequence, Union
import warnings

import google.api_core # type: ignore
from google.api_core import exceptions as core_exceptions # type: ignore
from google.api_core import gapic_v1 # type: ignore
from google.api_core import retry as retries # type: ignore
from google.api_core import version
import google.auth # type: ignore
from google.auth import credentials as ga_credentials # type: ignore
from google.longrunning import operations_pb2
Expand All @@ -30,6 +26,12 @@
from google.protobuf import empty_pb2, json_format # type: ignore
from grpc import Compression

import google.api_core # type: ignore
from google.api_core import exceptions as core_exceptions # type: ignore
from google.api_core import gapic_v1 # type: ignore
from google.api_core import general_helpers
from google.api_core import retry as retries # type: ignore
from google.api_core import version

PROTOBUF_VERSION = google.protobuf.__version__

Expand Down Expand Up @@ -69,9 +71,10 @@ def __init__(
credentials identify the application to the service; if none
are specified, the client will attempt to ascertain the
credentials from the environment.
credentials_file (Optional[str]): A file with credentials that can
credentials_file (Optional[str]): Deprecated. A file with credentials that can
be loaded with :func:`google.auth.load_credentials_from_file`.
This argument is mutually exclusive with credentials.
This argument is mutually exclusive with credentials. This argument will be
removed in the next major version of `google-api-core`.

.. warning::
Important: If you accept a credential configuration (credential JSON/File/Stream)
Expand All @@ -98,6 +101,9 @@ def __init__(
"https", but for testing or local servers,
"http" can be specified.
"""
if credentials_file is not None:
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
if maybe_url_match is None:
raise ValueError(
Expand Down
31 changes: 19 additions & 12 deletions google/api_core/operations_v1/transports/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,26 @@
#

from typing import Callable, Dict, Optional, Sequence, Tuple, Union
import warnings

from google.auth import credentials as ga_credentials # type: ignore
from google.auth.transport.requests import AuthorizedSession # type: ignore
from google.longrunning import operations_pb2 # type: ignore
import google.protobuf
from google.protobuf import empty_pb2 # type: ignore
from google.protobuf import json_format # type: ignore
import grpc
from requests import __version__ as requests_version

from google.api_core import exceptions as core_exceptions # type: ignore
from google.api_core import gapic_v1 # type: ignore
from google.api_core import general_helpers
from google.api_core import path_template # type: ignore
from google.api_core import rest_helpers # type: ignore
from google.api_core import retry as retries # type: ignore
from google.auth import credentials as ga_credentials # type: ignore
from google.auth.transport.requests import AuthorizedSession # type: ignore
from google.longrunning import operations_pb2 # type: ignore
from google.protobuf import empty_pb2 # type: ignore
from google.protobuf import json_format # type: ignore
import google.protobuf

import grpc
from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, OperationsTransport
from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO
from .base import OperationsTransport

PROTOBUF_VERSION = google.protobuf.__version__

Expand Down Expand Up @@ -91,19 +94,20 @@ def __init__(
are specified, the client will attempt to ascertain the
credentials from the environment.

credentials_file (Optional[str]): A file with credentials that can
credentials_file (Optional[str]): Deprecated. A file with credentials that can
be loaded with :func:`google.auth.load_credentials_from_file`.
This argument is ignored if ``channel`` is provided.
This argument is ignored if ``channel`` is provided. This argument will be
removed in the next major version of `google-api-core`.

.. warning::
Important: If you accept a credential configuration (credential JSON/File/Stream)
from an external source for authentication to Google Cloud Platform, you must
validate it before providing it to any Google API or client library. Providing an
unvalidated credential configuration to Google APIs or libraries can compromise
the security of your systems and data. For more information, refer to
`Validate credential configurations from external sources`_.
`Validate credential configuration from external sources`_.

.. _Validate credential configurations from external sources:
.. _Validate credential configuration from external sources:

https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
scopes (Optional(Sequence[str])): A list of scopes. This argument is
Expand All @@ -130,6 +134,9 @@ def __init__(
"v1" by default.

"""
if credentials_file is not None:
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

# Run the base constructor
# TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
# TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
Expand Down
21 changes: 21 additions & 0 deletions google/api_core/operations_v1/transports/rest_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import json
from typing import Any, Callable, Coroutine, Dict, Optional, Sequence, Tuple
import warnings

from google.auth import __version__ as auth_version

Expand All @@ -29,6 +30,7 @@

from google.api_core import exceptions as core_exceptions # type: ignore
from google.api_core import gapic_v1 # type: ignore
from google.api_core import general_helpers
from google.api_core import path_template # type: ignore
from google.api_core import rest_helpers # type: ignore
from google.api_core import retry_async as retries_async # type: ignore
Expand Down Expand Up @@ -96,6 +98,22 @@ def __init__(
credentials identify the application to the service; if none
are specified, the client will attempt to ascertain the
credentials from the environment.
credentials_file (Optional[str]): Deprecated. A file with credentials that can
be loaded with :func:`google.auth.load_credentials_from_file`.
This argument is ignored if ``channel`` is provided. This argument will be
removed in the next major version of `google-api-core`.

.. warning::
Important: If you accept a credential configuration (credential JSON/File/Stream)
from an external source for authentication to Google Cloud Platform, you must
validate it before providing it to any Google API or client library. Providing an
unvalidated credential configuration to Google APIs or libraries can compromise
the security of your systems and data. For more information, refer to
`Validate credential configurations from external sources`_.

.. _Validate credential configurations from external sources:

https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
client_info (google.api_core.gapic_v1.client_info.ClientInfo):
The client info used to send a user-agent string along with
API requests. If ``None``, then default info will be used.
Expand All @@ -113,6 +131,9 @@ def __init__(
"v1" by default.

"""
if credentials_file is not None:
warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)

unsupported_params = {
# TODO(https://github.com/googleapis/python-api-core/issues/715): Add support for `credentials_file` to async REST transport.
"google.api_core.client_options.ClientOptions.credentials_file": credentials_file,
Expand Down
35 changes: 33 additions & 2 deletions tests/unit/operations_v1/test_operations_rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,22 @@ def test_operations_client_client_options(
always_use_jwt_access=True,
)

# Check the case credentials_file is provided
options = client_options.ClientOptions(credentials_file="credentials.json")
with mock.patch.object(transport_class, "__init__") as patched:
patched.return_value = None
client = client_class(client_options=options, transport=transport_name)
patched.assert_called_once_with(
credentials=None,
credentials_file="credentials.json",
host=client.DEFAULT_ENDPOINT,
scopes=None,
client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
always_use_jwt_access=True,
)


# TODO: Add support for mtls in async REST
@pytest.mark.parametrize(
Expand Down Expand Up @@ -544,8 +560,23 @@ def test_operations_client_client_options_credentials_file(
)


def test_list_operations_rest():
client = _get_operations_client(is_async=False)
@pytest.mark.parametrize(
"credentials_file",
[None, "credentials.json"],
)
@mock.patch(
"google.auth.default",
autospec=True,
return_value=(mock.sentinel.credentials, mock.sentinel.project),
)
def test_list_operations_rest(google_auth_default, credentials_file):
sync_transport = transports.rest.OperationsRestTransport(
credentials_file=credentials_file,
http_options=HTTP_OPTIONS,
)

client = AbstractOperationsClient(transport=sync_transport)

# Mock the http request call within the method and fake a response.
with mock.patch.object(_get_session_type(is_async=False), "request") as req:
# Designate an appropriate value for the returned response.
Expand Down