diff --git a/google/auth/_credentials_base.py b/google/auth/_credentials_base.py new file mode 100644 index 000000000..29462dc0c --- /dev/null +++ b/google/auth/_credentials_base.py @@ -0,0 +1,73 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interface for base credentials.""" + +import abc + +from google.auth import _helpers + + +class _BaseCredentials(metaclass=abc.ABCMeta): + """Base class for all credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + def __init__(self): + self.token = None + """str: The bearer token that can be used in HTTP headers to make + authenticated requests.""" + + @abc.abstractmethod + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Refresh must be implemented") + + def _apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["authorization"] = "Bearer {}".format( + _helpers.from_bytes(token or self.token) + ) diff --git a/google/auth/credentials.py b/google/auth/credentials.py index 27abd443d..e31930311 100644 --- a/google/auth/credentials.py +++ b/google/auth/credentials.py @@ -22,12 +22,13 @@ from google.auth import _helpers, environment_vars from google.auth import exceptions from google.auth import metrics +from google.auth._credentials_base import _BaseCredentials from google.auth._refresh_worker import RefreshThreadManager DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" -class Credentials(metaclass=abc.ABCMeta): +class Credentials(_BaseCredentials): """Base class for all credentials. All credentials have a :attr:`token` that is used for authentication and @@ -47,9 +48,8 @@ class Credentials(metaclass=abc.ABCMeta): """ def __init__(self): - self.token = None - """str: The bearer token that can be used in HTTP headers to make - authenticated requests.""" + super(Credentials, self).__init__() + self.expiry = None """Optional[datetime]: When the token expires and is no longer valid. If this is None, the token is assumed to never expire.""" @@ -167,9 +167,7 @@ def apply(self, headers, token=None): token (Optional[str]): If specified, overrides the current access token. """ - headers["authorization"] = "Bearer {}".format( - _helpers.from_bytes(token or self.token) - ) + self._apply(headers, token=token) """Trust boundary value will be a cached value from global lookup. The response of trust boundary will be a list of regions and a hex diff --git a/google/auth/transport/_requests_base.py b/google/auth/transport/_requests_base.py new file mode 100644 index 000000000..ec718d909 --- /dev/null +++ b/google/auth/transport/_requests_base.py @@ -0,0 +1,52 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Base Requests.""" + + +import abc + + +_DEFAULT_TIMEOUT = 120 # in second + + +class _BaseAuthorizedSession(metaclass=abc.ABCMeta): + """Base class for a Request Session with credentials. This class is intended to capture + the common logic between synchronous and asynchronous request sessions and is not intended to + be instantiated directly. + + Args: + credentials (google.auth._credentials_base.BaseCredentials): The credentials to + add to the request. + """ + + def __init__(self, credentials): + self.credentials = credentials + + @abc.abstractmethod + def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs + ): + raise NotImplementedError("Request must be implemented") + + @abc.abstractmethod + def close(self): + raise NotImplementedError("Close must be implemented") diff --git a/google/auth/transport/requests.py b/google/auth/transport/requests.py index 23a69783d..68f67c59b 100644 --- a/google/auth/transport/requests.py +++ b/google/auth/transport/requests.py @@ -38,6 +38,7 @@ from google.auth import exceptions from google.auth import transport import google.auth.transport._mtls_helper +from google.auth.transport._requests_base import _BaseAuthorizedSession from google.oauth2 import service_account _LOGGER = logging.getLogger(__name__) @@ -292,7 +293,7 @@ def proxy_manager_for(self, *args, **kwargs): return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) -class AuthorizedSession(requests.Session): +class AuthorizedSession(requests.Session, _BaseAuthorizedSession): """A Requests Session class with credentials. This class is used to perform requests to API endpoints that require @@ -389,7 +390,7 @@ def __init__( default_host=None, ): super(AuthorizedSession, self).__init__() - self.credentials = credentials + _BaseAuthorizedSession.__init__(self, credentials) self._refresh_status_codes = refresh_status_codes self._max_refresh_attempts = max_refresh_attempts self._refresh_timeout = refresh_timeout diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc index 3d9bd3327..77ab87e0f 100644 Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ