aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/google-auth/py2/google/oauth2
diff options
context:
space:
mode:
authoralexv-smirnov <alex@ydb.tech>2023-12-01 12:02:50 +0300
committeralexv-smirnov <alex@ydb.tech>2023-12-01 13:28:10 +0300
commit0e578a4c44d4abd539d9838347b9ebafaca41dfb (patch)
treea0c1969c37f818c830ebeff9c077eacf30be6ef8 /contrib/python/google-auth/py2/google/oauth2
parent84f2d3d4cc985e63217cff149bd2e6d67ae6fe22 (diff)
downloadydb-0e578a4c44d4abd539d9838347b9ebafaca41dfb.tar.gz
Change "ya.make"
Diffstat (limited to 'contrib/python/google-auth/py2/google/oauth2')
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/__init__.py15
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/_client.py327
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/challenges.py157
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/credentials.py479
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/id_token.py264
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/reauth.py341
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/service_account.py685
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/sts.py155
-rw-r--r--contrib/python/google-auth/py2/google/oauth2/utils.py171
9 files changed, 2594 insertions, 0 deletions
diff --git a/contrib/python/google-auth/py2/google/oauth2/__init__.py b/contrib/python/google-auth/py2/google/oauth2/__init__.py
new file mode 100644
index 0000000000..4fb71fd1ad
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2016 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.
+
+"""Google OAuth 2.0 Library for Python."""
diff --git a/contrib/python/google-auth/py2/google/oauth2/_client.py b/contrib/python/google-auth/py2/google/oauth2/_client.py
new file mode 100644
index 0000000000..2f4e8474b5
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/_client.py
@@ -0,0 +1,327 @@
+# Copyright 2016 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.
+
+"""OAuth 2.0 client.
+
+This is a client for interacting with an OAuth 2.0 authorization server's
+token endpoint.
+
+For more information about the token endpoint, see
+`Section 3.1 of rfc6749`_
+
+.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2
+"""
+
+import datetime
+import json
+
+import six
+from six.moves import http_client
+from six.moves import urllib
+
+from google.auth import _helpers
+from google.auth import exceptions
+from google.auth import jwt
+
+_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
+_JSON_CONTENT_TYPE = "application/json"
+_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
+_REFRESH_GRANT_TYPE = "refresh_token"
+
+
+def _handle_error_response(response_data):
+ """Translates an error response into an exception.
+
+ Args:
+ response_data (Mapping): The decoded response data.
+
+ Raises:
+ google.auth.exceptions.RefreshError: The errors contained in response_data.
+ """
+ try:
+ error_details = "{}: {}".format(
+ response_data["error"], response_data.get("error_description")
+ )
+ # If no details could be extracted, use the response data.
+ except (KeyError, ValueError):
+ error_details = json.dumps(response_data)
+
+ raise exceptions.RefreshError(error_details, response_data)
+
+
+def _parse_expiry(response_data):
+ """Parses the expiry field from a response into a datetime.
+
+ Args:
+ response_data (Mapping): The JSON-parsed response data.
+
+ Returns:
+ Optional[datetime]: The expiration or ``None`` if no expiration was
+ specified.
+ """
+ expires_in = response_data.get("expires_in", None)
+
+ if expires_in is not None:
+ return _helpers.utcnow() + datetime.timedelta(seconds=expires_in)
+ else:
+ return None
+
+
+def _token_endpoint_request_no_throw(
+ request, token_uri, body, access_token=None, use_json=False
+):
+ """Makes a request to the OAuth 2.0 authorization server's token endpoint.
+ This function doesn't throw on response errors.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
+ URI.
+ body (Mapping[str, str]): The parameters to send in the request body.
+ access_token (Optional(str)): The access token needed to make the request.
+ use_json (Optional(bool)): Use urlencoded format or json format for the
+ content type. The default value is False.
+
+ Returns:
+ Tuple(bool, Mapping[str, str]): A boolean indicating if the request is
+ successful, and a mapping for the JSON-decoded response data.
+ """
+ if use_json:
+ headers = {"Content-Type": _JSON_CONTENT_TYPE}
+ body = json.dumps(body).encode("utf-8")
+ else:
+ headers = {"Content-Type": _URLENCODED_CONTENT_TYPE}
+ body = urllib.parse.urlencode(body).encode("utf-8")
+
+ if access_token:
+ headers["Authorization"] = "Bearer {}".format(access_token)
+
+ retry = 0
+ # retry to fetch token for maximum of two times if any internal failure
+ # occurs.
+ while True:
+ response = request(method="POST", url=token_uri, headers=headers, body=body)
+ response_body = (
+ response.data.decode("utf-8")
+ if hasattr(response.data, "decode")
+ else response.data
+ )
+ response_data = json.loads(response_body)
+
+ if response.status == http_client.OK:
+ break
+ else:
+ error_desc = response_data.get("error_description") or ""
+ error_code = response_data.get("error") or ""
+ if (
+ any(e == "internal_failure" for e in (error_code, error_desc))
+ and retry < 1
+ ):
+ retry += 1
+ continue
+ return response.status == http_client.OK, response_data
+
+ return response.status == http_client.OK, response_data
+
+
+def _token_endpoint_request(
+ request, token_uri, body, access_token=None, use_json=False
+):
+ """Makes a request to the OAuth 2.0 authorization server's token endpoint.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
+ URI.
+ body (Mapping[str, str]): The parameters to send in the request body.
+ access_token (Optional(str)): The access token needed to make the request.
+ use_json (Optional(bool)): Use urlencoded format or json format for the
+ content type. The default value is False.
+
+ Returns:
+ Mapping[str, str]: The JSON-decoded response data.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the token endpoint returned
+ an error.
+ """
+ response_status_ok, response_data = _token_endpoint_request_no_throw(
+ request, token_uri, body, access_token=access_token, use_json=use_json
+ )
+ if not response_status_ok:
+ _handle_error_response(response_data)
+ return response_data
+
+
+def jwt_grant(request, token_uri, assertion):
+ """Implements the JWT Profile for OAuth 2.0 Authorization Grants.
+
+ For more details, see `rfc7523 section 4`_.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
+ URI.
+ assertion (str): The OAuth 2.0 assertion.
+
+ Returns:
+ Tuple[str, Optional[datetime], Mapping[str, str]]: The access token,
+ expiration, and additional data returned by the token endpoint.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the token endpoint returned
+ an error.
+
+ .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4
+ """
+ body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}
+
+ response_data = _token_endpoint_request(request, token_uri, body)
+
+ try:
+ access_token = response_data["access_token"]
+ except KeyError as caught_exc:
+ new_exc = exceptions.RefreshError("No access token in response.", response_data)
+ six.raise_from(new_exc, caught_exc)
+
+ expiry = _parse_expiry(response_data)
+
+ return access_token, expiry, response_data
+
+
+def id_token_jwt_grant(request, token_uri, assertion):
+ """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but
+ requests an OpenID Connect ID Token instead of an access token.
+
+ This is a variant on the standard JWT Profile that is currently unique
+ to Google. This was added for the benefit of authenticating to services
+ that require ID Tokens instead of access tokens or JWT bearer tokens.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ token_uri (str): The OAuth 2.0 authorization server's token endpoint
+ URI.
+ assertion (str): JWT token signed by a service account. The token's
+ payload must include a ``target_audience`` claim.
+
+ Returns:
+ Tuple[str, Optional[datetime], Mapping[str, str]]:
+ The (encoded) Open ID Connect ID Token, expiration, and additional
+ data returned by the endpoint.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the token endpoint returned
+ an error.
+ """
+ body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE}
+
+ response_data = _token_endpoint_request(request, token_uri, body)
+
+ try:
+ id_token = response_data["id_token"]
+ except KeyError as caught_exc:
+ new_exc = exceptions.RefreshError("No ID token in response.", response_data)
+ six.raise_from(new_exc, caught_exc)
+
+ payload = jwt.decode(id_token, verify=False)
+ expiry = datetime.datetime.utcfromtimestamp(payload["exp"])
+
+ return id_token, expiry, response_data
+
+
+def _handle_refresh_grant_response(response_data, refresh_token):
+ """Extract tokens from refresh grant response.
+
+ Args:
+ response_data (Mapping[str, str]): Refresh grant response data.
+ refresh_token (str): Current refresh token.
+
+ Returns:
+ Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token,
+ refresh token, expiration, and additional data returned by the token
+ endpoint. If response_data doesn't have refresh token, then the current
+ refresh token will be returned.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the token endpoint returned
+ an error.
+ """
+ try:
+ access_token = response_data["access_token"]
+ except KeyError as caught_exc:
+ new_exc = exceptions.RefreshError("No access token in response.", response_data)
+ six.raise_from(new_exc, caught_exc)
+
+ refresh_token = response_data.get("refresh_token", refresh_token)
+ expiry = _parse_expiry(response_data)
+
+ return access_token, refresh_token, expiry, response_data
+
+
+def refresh_grant(
+ request,
+ token_uri,
+ refresh_token,
+ client_id,
+ client_secret,
+ scopes=None,
+ rapt_token=None,
+):
+ """Implements the OAuth 2.0 refresh token grant.
+
+ For more details, see `rfc678 section 6`_.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
+ URI.
+ refresh_token (str): The refresh token to use to get a new access
+ token.
+ client_id (str): The OAuth 2.0 application's client ID.
+ client_secret (str): The Oauth 2.0 appliaction's client secret.
+ scopes (Optional(Sequence[str])): Scopes to request. If present, all
+ scopes must be authorized for the refresh token. Useful if refresh
+ token has a wild card scope (e.g.
+ 'https://www.googleapis.com/auth/any-api').
+ rapt_token (Optional(str)): The reauth Proof Token.
+
+ Returns:
+ Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access
+ token, new or current refresh token, expiration, and additional data
+ returned by the token endpoint.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the token endpoint returned
+ an error.
+
+ .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6
+ """
+ body = {
+ "grant_type": _REFRESH_GRANT_TYPE,
+ "client_id": client_id,
+ "client_secret": client_secret,
+ "refresh_token": refresh_token,
+ }
+ if scopes:
+ body["scope"] = " ".join(scopes)
+ if rapt_token:
+ body["rapt"] = rapt_token
+
+ response_data = _token_endpoint_request(request, token_uri, body)
+ return _handle_refresh_grant_response(response_data, refresh_token)
diff --git a/contrib/python/google-auth/py2/google/oauth2/challenges.py b/contrib/python/google-auth/py2/google/oauth2/challenges.py
new file mode 100644
index 0000000000..d0b070eda6
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/challenges.py
@@ -0,0 +1,157 @@
+# Copyright 2021 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.
+
+""" Challenges for reauthentication.
+"""
+
+import abc
+import base64
+import getpass
+import sys
+
+import six
+
+from google.auth import _helpers
+from google.auth import exceptions
+
+
+REAUTH_ORIGIN = "https://accounts.google.com"
+
+
+def get_user_password(text):
+ """Get password from user.
+
+ Override this function with a different logic if you are using this library
+ outside a CLI.
+
+ Args:
+ text (str): message for the password prompt.
+
+ Returns:
+ str: password string.
+ """
+ return getpass.getpass(text)
+
+
+@six.add_metaclass(abc.ABCMeta)
+class ReauthChallenge(object):
+ """Base class for reauth challenges."""
+
+ @property
+ @abc.abstractmethod
+ def name(self): # pragma: NO COVER
+ """Returns the name of the challenge."""
+ raise NotImplementedError("name property must be implemented")
+
+ @property
+ @abc.abstractmethod
+ def is_locally_eligible(self): # pragma: NO COVER
+ """Returns true if a challenge is supported locally on this machine."""
+ raise NotImplementedError("is_locally_eligible property must be implemented")
+
+ @abc.abstractmethod
+ def obtain_challenge_input(self, metadata): # pragma: NO COVER
+ """Performs logic required to obtain credentials and returns it.
+
+ Args:
+ metadata (Mapping): challenge metadata returned in the 'challenges' field in
+ the initial reauth request. Includes the 'challengeType' field
+ and other challenge-specific fields.
+
+ Returns:
+ response that will be send to the reauth service as the content of
+ the 'proposalResponse' field in the request body. Usually a dict
+ with the keys specific to the challenge. For example,
+ ``{'credential': password}`` for password challenge.
+ """
+ raise NotImplementedError("obtain_challenge_input method must be implemented")
+
+
+class PasswordChallenge(ReauthChallenge):
+ """Challenge that asks for user's password."""
+
+ @property
+ def name(self):
+ return "PASSWORD"
+
+ @property
+ def is_locally_eligible(self):
+ return True
+
+ @_helpers.copy_docstring(ReauthChallenge)
+ def obtain_challenge_input(self, unused_metadata):
+ passwd = get_user_password("Please enter your password:")
+ if not passwd:
+ passwd = " " # avoid the server crashing in case of no password :D
+ return {"credential": passwd}
+
+
+class SecurityKeyChallenge(ReauthChallenge):
+ """Challenge that asks for user's security key touch."""
+
+ @property
+ def name(self):
+ return "SECURITY_KEY"
+
+ @property
+ def is_locally_eligible(self):
+ return True
+
+ @_helpers.copy_docstring(ReauthChallenge)
+ def obtain_challenge_input(self, metadata):
+ try:
+ import pyu2f.convenience.authenticator
+ import pyu2f.errors
+ import pyu2f.model
+ except ImportError:
+ raise exceptions.ReauthFailError(
+ "pyu2f dependency is required to use Security key reauth feature. "
+ "It can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`."
+ )
+ sk = metadata["securityKey"]
+ challenges = sk["challenges"]
+ app_id = sk["applicationId"]
+
+ challenge_data = []
+ for c in challenges:
+ kh = c["keyHandle"].encode("ascii")
+ key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh)))
+ challenge = c["challenge"].encode("ascii")
+ challenge = base64.urlsafe_b64decode(challenge)
+ challenge_data.append({"key": key, "challenge": challenge})
+
+ try:
+ api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator(
+ REAUTH_ORIGIN
+ )
+ response = api.Authenticate(
+ app_id, challenge_data, print_callback=sys.stderr.write
+ )
+ return {"securityKey": response}
+ except pyu2f.errors.U2FError as e:
+ if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE:
+ sys.stderr.write("Ineligible security key.\n")
+ elif e.code == pyu2f.errors.U2FError.TIMEOUT:
+ sys.stderr.write("Timed out while waiting for security key touch.\n")
+ else:
+ raise e
+ except pyu2f.errors.NoDeviceFoundError:
+ sys.stderr.write("No security key found.\n")
+ return None
+
+
+AVAILABLE_CHALLENGES = {
+ challenge.name: challenge
+ for challenge in [SecurityKeyChallenge(), PasswordChallenge()]
+}
diff --git a/contrib/python/google-auth/py2/google/oauth2/credentials.py b/contrib/python/google-auth/py2/google/oauth2/credentials.py
new file mode 100644
index 0000000000..158249ed5f
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/credentials.py
@@ -0,0 +1,479 @@
+# Copyright 2016 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.
+
+"""OAuth 2.0 Credentials.
+
+This module provides credentials based on OAuth 2.0 access and refresh tokens.
+These credentials usually access resources on behalf of a user (resource
+owner).
+
+Specifically, this is intended to use access tokens acquired using the
+`Authorization Code grant`_ and can refresh those tokens using a
+optional `refresh token`_.
+
+Obtaining the initial access and refresh token is outside of the scope of this
+module. Consult `rfc6749 section 4.1`_ for complete details on the
+Authorization Code grant flow.
+
+.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
+.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
+.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
+"""
+
+from datetime import datetime
+import io
+import json
+
+import six
+
+from google.auth import _cloud_sdk
+from google.auth import _helpers
+from google.auth import credentials
+from google.auth import exceptions
+from google.oauth2 import reauth
+
+
+# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
+_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
+
+
+class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):
+ """Credentials using OAuth 2.0 access and refresh tokens.
+
+ The credentials are considered immutable. If you want to modify the
+ quota project, use :meth:`with_quota_project` or ::
+
+ credentials = credentials.with_quota_project('myproject-123)
+
+ If reauth is enabled, `pyu2f` dependency has to be installed in order to use security
+ key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install
+ google-auth[reauth]`.
+ """
+
+ def __init__(
+ self,
+ token,
+ refresh_token=None,
+ id_token=None,
+ token_uri=None,
+ client_id=None,
+ client_secret=None,
+ scopes=None,
+ default_scopes=None,
+ quota_project_id=None,
+ expiry=None,
+ rapt_token=None,
+ refresh_handler=None,
+ ):
+ """
+ Args:
+ token (Optional(str)): The OAuth 2.0 access token. Can be None
+ if refresh information is provided.
+ refresh_token (str): The OAuth 2.0 refresh token. If specified,
+ credentials can be refreshed.
+ id_token (str): The Open ID Connect ID Token.
+ token_uri (str): The OAuth 2.0 authorization server's token
+ endpoint URI. Must be specified for refresh, can be left as
+ None if the token can not be refreshed.
+ client_id (str): The OAuth 2.0 client ID. Must be specified for
+ refresh, can be left as None if the token can not be refreshed.
+ client_secret(str): The OAuth 2.0 client secret. Must be specified
+ for refresh, can be left as None if the token can not be
+ refreshed.
+ scopes (Sequence[str]): The scopes used to obtain authorization.
+ This parameter is used by :meth:`has_scopes`. OAuth 2.0
+ credentials can not request additional scopes after
+ authorization. The scopes must be derivable from the refresh
+ token if refresh information is provided (e.g. The refresh
+ token scopes are a superset of this or contain a wild card
+ scope like 'https://www.googleapis.com/auth/any-api').
+ default_scopes (Sequence[str]): Default scopes passed by a
+ Google client library. Use 'scopes' for user-defined scopes.
+ quota_project_id (Optional[str]): The project ID used for quota and billing.
+ This project may be different from the project used to
+ create the credentials.
+ rapt_token (Optional[str]): The reauth Proof Token.
+ refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
+ A callable which takes in the HTTP request callable and the list of
+ OAuth scopes and when called returns an access token string for the
+ requested scopes and its expiry datetime. This is useful when no
+ refresh tokens are provided and tokens are obtained by calling
+ some external process on demand. It is particularly useful for
+ retrieving downscoped tokens from a token broker.
+ """
+ super(Credentials, self).__init__()
+ self.token = token
+ self.expiry = expiry
+ self._refresh_token = refresh_token
+ self._id_token = id_token
+ self._scopes = scopes
+ self._default_scopes = default_scopes
+ self._token_uri = token_uri
+ self._client_id = client_id
+ self._client_secret = client_secret
+ self._quota_project_id = quota_project_id
+ self._rapt_token = rapt_token
+ self.refresh_handler = refresh_handler
+
+ def __getstate__(self):
+ """A __getstate__ method must exist for the __setstate__ to be called
+ This is identical to the default implementation.
+ See https://docs.python.org/3.7/library/pickle.html#object.__setstate__
+ """
+ state_dict = self.__dict__.copy()
+ # Remove _refresh_handler function as there are limitations pickling and
+ # unpickling certain callables (lambda, functools.partial instances)
+ # because they need to be importable.
+ # Instead, the refresh_handler setter should be used to repopulate this.
+ del state_dict["_refresh_handler"]
+ return state_dict
+
+ def __setstate__(self, d):
+ """Credentials pickled with older versions of the class do not have
+ all the attributes."""
+ self.token = d.get("token")
+ self.expiry = d.get("expiry")
+ self._refresh_token = d.get("_refresh_token")
+ self._id_token = d.get("_id_token")
+ self._scopes = d.get("_scopes")
+ self._default_scopes = d.get("_default_scopes")
+ self._token_uri = d.get("_token_uri")
+ self._client_id = d.get("_client_id")
+ self._client_secret = d.get("_client_secret")
+ self._quota_project_id = d.get("_quota_project_id")
+ self._rapt_token = d.get("_rapt_token")
+ # The refresh_handler setter should be used to repopulate this.
+ self._refresh_handler = None
+
+ @property
+ def refresh_token(self):
+ """Optional[str]: The OAuth 2.0 refresh token."""
+ return self._refresh_token
+
+ @property
+ def scopes(self):
+ """Optional[str]: The OAuth 2.0 permission scopes."""
+ return self._scopes
+
+ @property
+ def token_uri(self):
+ """Optional[str]: The OAuth 2.0 authorization server's token endpoint
+ URI."""
+ return self._token_uri
+
+ @property
+ def id_token(self):
+ """Optional[str]: The Open ID Connect ID Token.
+
+ Depending on the authorization server and the scopes requested, this
+ may be populated when credentials are obtained and updated when
+ :meth:`refresh` is called. This token is a JWT. It can be verified
+ and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.
+ """
+ return self._id_token
+
+ @property
+ def client_id(self):
+ """Optional[str]: The OAuth 2.0 client ID."""
+ return self._client_id
+
+ @property
+ def client_secret(self):
+ """Optional[str]: The OAuth 2.0 client secret."""
+ return self._client_secret
+
+ @property
+ def requires_scopes(self):
+ """False: OAuth 2.0 credentials have their scopes set when
+ the initial token is requested and can not be changed."""
+ return False
+
+ @property
+ def rapt_token(self):
+ """Optional[str]: The reauth Proof Token."""
+ return self._rapt_token
+
+ @property
+ def refresh_handler(self):
+ """Returns the refresh handler if available.
+
+ Returns:
+ Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]:
+ The current refresh handler.
+ """
+ return self._refresh_handler
+
+ @refresh_handler.setter
+ def refresh_handler(self, value):
+ """Updates the current refresh handler.
+
+ Args:
+ value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
+ The updated value of the refresh handler.
+
+ Raises:
+ TypeError: If the value is not a callable or None.
+ """
+ if not callable(value) and value is not None:
+ raise TypeError("The provided refresh_handler is not a callable or None.")
+ self._refresh_handler = value
+
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
+ def with_quota_project(self, quota_project_id):
+
+ return self.__class__(
+ self.token,
+ refresh_token=self.refresh_token,
+ id_token=self.id_token,
+ token_uri=self.token_uri,
+ client_id=self.client_id,
+ client_secret=self.client_secret,
+ scopes=self.scopes,
+ default_scopes=self.default_scopes,
+ quota_project_id=quota_project_id,
+ rapt_token=self.rapt_token,
+ )
+
+ @_helpers.copy_docstring(credentials.Credentials)
+ def refresh(self, request):
+ scopes = self._scopes if self._scopes is not None else self._default_scopes
+ # Use refresh handler if available and no refresh token is
+ # available. This is useful in general when tokens are obtained by calling
+ # some external process on demand. It is particularly useful for retrieving
+ # downscoped tokens from a token broker.
+ if self._refresh_token is None and self.refresh_handler:
+ token, expiry = self.refresh_handler(request, scopes=scopes)
+ # Validate returned data.
+ if not isinstance(token, str):
+ raise exceptions.RefreshError(
+ "The refresh_handler returned token is not a string."
+ )
+ if not isinstance(expiry, datetime):
+ raise exceptions.RefreshError(
+ "The refresh_handler returned expiry is not a datetime object."
+ )
+ if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW:
+ raise exceptions.RefreshError(
+ "The credentials returned by the refresh_handler are "
+ "already expired."
+ )
+ self.token = token
+ self.expiry = expiry
+ return
+
+ if (
+ self._refresh_token is None
+ or self._token_uri is None
+ or self._client_id is None
+ or self._client_secret is None
+ ):
+ raise exceptions.RefreshError(
+ "The credentials do not contain the necessary fields need to "
+ "refresh the access token. You must specify refresh_token, "
+ "token_uri, client_id, and client_secret."
+ )
+
+ (
+ access_token,
+ refresh_token,
+ expiry,
+ grant_response,
+ rapt_token,
+ ) = reauth.refresh_grant(
+ request,
+ self._token_uri,
+ self._refresh_token,
+ self._client_id,
+ self._client_secret,
+ scopes=scopes,
+ rapt_token=self._rapt_token,
+ )
+
+ self.token = access_token
+ self.expiry = expiry
+ self._refresh_token = refresh_token
+ self._id_token = grant_response.get("id_token")
+ self._rapt_token = rapt_token
+
+ if scopes and "scope" in grant_response:
+ requested_scopes = frozenset(scopes)
+ granted_scopes = frozenset(grant_response["scope"].split())
+ scopes_requested_but_not_granted = requested_scopes - granted_scopes
+ if scopes_requested_but_not_granted:
+ raise exceptions.RefreshError(
+ "Not all requested scopes were granted by the "
+ "authorization server, missing scopes {}.".format(
+ ", ".join(scopes_requested_but_not_granted)
+ )
+ )
+
+ @classmethod
+ def from_authorized_user_info(cls, info, scopes=None):
+ """Creates a Credentials instance from parsed authorized user info.
+
+ Args:
+ info (Mapping[str, str]): The authorized user info in Google
+ format.
+ scopes (Sequence[str]): Optional list of scopes to include in the
+ credentials.
+
+ Returns:
+ google.oauth2.credentials.Credentials: The constructed
+ credentials.
+
+ Raises:
+ ValueError: If the info is not in the expected format.
+ """
+ keys_needed = set(("refresh_token", "client_id", "client_secret"))
+ missing = keys_needed.difference(six.iterkeys(info))
+
+ if missing:
+ raise ValueError(
+ "Authorized user info was not in the expected format, missing "
+ "fields {}.".format(", ".join(missing))
+ )
+
+ # access token expiry (datetime obj); auto-expire if not saved
+ expiry = info.get("expiry")
+ if expiry:
+ expiry = datetime.strptime(
+ expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"
+ )
+ else:
+ expiry = _helpers.utcnow() - _helpers.CLOCK_SKEW
+
+ # process scopes, which needs to be a seq
+ if scopes is None and "scopes" in info:
+ scopes = info.get("scopes")
+ if isinstance(scopes, str):
+ scopes = scopes.split(" ")
+
+ return cls(
+ token=info.get("token"),
+ refresh_token=info.get("refresh_token"),
+ token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, # always overrides
+ scopes=scopes,
+ client_id=info.get("client_id"),
+ client_secret=info.get("client_secret"),
+ quota_project_id=info.get("quota_project_id"), # may not exist
+ expiry=expiry,
+ )
+
+ @classmethod
+ def from_authorized_user_file(cls, filename, scopes=None):
+ """Creates a Credentials instance from an authorized user json file.
+
+ Args:
+ filename (str): The path to the authorized user json file.
+ scopes (Sequence[str]): Optional list of scopes to include in the
+ credentials.
+
+ Returns:
+ google.oauth2.credentials.Credentials: The constructed
+ credentials.
+
+ Raises:
+ ValueError: If the file is not in the expected format.
+ """
+ with io.open(filename, "r", encoding="utf-8") as json_file:
+ data = json.load(json_file)
+ return cls.from_authorized_user_info(data, scopes)
+
+ def to_json(self, strip=None):
+ """Utility function that creates a JSON representation of a Credentials
+ object.
+
+ Args:
+ strip (Sequence[str]): Optional list of members to exclude from the
+ generated JSON.
+
+ Returns:
+ str: A JSON representation of this instance. When converted into
+ a dictionary, it can be passed to from_authorized_user_info()
+ to create a new credential instance.
+ """
+ prep = {
+ "token": self.token,
+ "refresh_token": self.refresh_token,
+ "token_uri": self.token_uri,
+ "client_id": self.client_id,
+ "client_secret": self.client_secret,
+ "scopes": self.scopes,
+ "rapt_token": self.rapt_token,
+ }
+ if self.expiry: # flatten expiry timestamp
+ prep["expiry"] = self.expiry.isoformat() + "Z"
+
+ # Remove empty entries (those which are None)
+ prep = {k: v for k, v in prep.items() if v is not None}
+
+ # Remove entries that explicitely need to be removed
+ if strip is not None:
+ prep = {k: v for k, v in prep.items() if k not in strip}
+
+ return json.dumps(prep)
+
+
+class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):
+ """Access token credentials for user account.
+
+ Obtain the access token for a given user account or the current active
+ user account with the ``gcloud auth print-access-token`` command.
+
+ Args:
+ account (Optional[str]): Account to get the access token for. If not
+ specified, the current active account will be used.
+ quota_project_id (Optional[str]): The project ID used for quota
+ and billing.
+ """
+
+ def __init__(self, account=None, quota_project_id=None):
+ super(UserAccessTokenCredentials, self).__init__()
+ self._account = account
+ self._quota_project_id = quota_project_id
+
+ def with_account(self, account):
+ """Create a new instance with the given account.
+
+ Args:
+ account (str): Account to get the access token for.
+
+ Returns:
+ google.oauth2.credentials.UserAccessTokenCredentials: The created
+ credentials with the given account.
+ """
+ return self.__class__(account=account, quota_project_id=self._quota_project_id)
+
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
+ def with_quota_project(self, quota_project_id):
+ return self.__class__(account=self._account, quota_project_id=quota_project_id)
+
+ def refresh(self, request):
+ """Refreshes the access token.
+
+ Args:
+ request (google.auth.transport.Request): This argument is required
+ by the base class interface but not used in this implementation,
+ so just set it to `None`.
+
+ Raises:
+ google.auth.exceptions.UserAccessTokenError: If the access token
+ refresh failed.
+ """
+ self.token = _cloud_sdk.get_auth_access_token(self._account)
+
+ @_helpers.copy_docstring(credentials.Credentials)
+ def before_request(self, request, method, url, headers):
+ self.refresh(request)
+ self.apply(headers)
diff --git a/contrib/python/google-auth/py2/google/oauth2/id_token.py b/contrib/python/google-auth/py2/google/oauth2/id_token.py
new file mode 100644
index 0000000000..540ccd1251
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/id_token.py
@@ -0,0 +1,264 @@
+# Copyright 2016 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.
+
+"""Google ID Token helpers.
+
+Provides support for verifying `OpenID Connect ID Tokens`_, especially ones
+generated by Google infrastructure.
+
+To parse and verify an ID Token issued by Google's OAuth 2.0 authorization
+server use :func:`verify_oauth2_token`. To verify an ID Token issued by
+Firebase, use :func:`verify_firebase_token`.
+
+A general purpose ID Token verifier is available as :func:`verify_token`.
+
+Example::
+
+ from google.oauth2 import id_token
+ from google.auth.transport import requests
+
+ request = requests.Request()
+
+ id_info = id_token.verify_oauth2_token(
+ token, request, 'my-client-id.example.com')
+
+ userid = id_info['sub']
+
+By default, this will re-fetch certificates for each verification. Because
+Google's public keys are only changed infrequently (on the order of once per
+day), you may wish to take advantage of caching to reduce latency and the
+potential for network errors. This can be accomplished using an external
+library like `CacheControl`_ to create a cache-aware
+:class:`google.auth.transport.Request`::
+
+ import cachecontrol
+ import google.auth.transport.requests
+ import requests
+
+ session = requests.session()
+ cached_session = cachecontrol.CacheControl(session)
+ request = google.auth.transport.requests.Request(session=cached_session)
+
+.. _OpenID Connect ID Tokens:
+ http://openid.net/specs/openid-connect-core-1_0.html#IDToken
+.. _CacheControl: https://cachecontrol.readthedocs.io
+"""
+
+import json
+import os
+
+import six
+from six.moves import http_client
+
+from google.auth import environment_vars
+from google.auth import exceptions
+from google.auth import jwt
+
+
+# The URL that provides public certificates for verifying ID tokens issued
+# by Google's OAuth 2.0 authorization server.
+_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs"
+
+# The URL that provides public certificates for verifying ID tokens issued
+# by Firebase and the Google APIs infrastructure
+_GOOGLE_APIS_CERTS_URL = (
+ "https://www.googleapis.com/robot/v1/metadata/x509"
+ "/securetoken@system.gserviceaccount.com"
+)
+
+_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"]
+
+
+def _fetch_certs(request, certs_url):
+ """Fetches certificates.
+
+ Google-style cerificate endpoints return JSON in the format of
+ ``{'key id': 'x509 certificate'}``.
+
+ Args:
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
+ certs_url (str): The certificate endpoint URL.
+
+ Returns:
+ Mapping[str, str]: A mapping of public key ID to x.509 certificate
+ data.
+ """
+ response = request(certs_url, method="GET")
+
+ if response.status != http_client.OK:
+ raise exceptions.TransportError(
+ "Could not fetch certificates at {}".format(certs_url)
+ )
+
+ return json.loads(response.data.decode("utf-8"))
+
+
+def verify_token(id_token, request, audience=None, certs_url=_GOOGLE_OAUTH2_CERTS_URL):
+ """Verifies an ID token and returns the decoded token.
+
+ Args:
+ id_token (Union[str, bytes]): The encoded token.
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
+ audience (str or list): The audience or audiences that this token is
+ intended for. If None then the audience is not verified.
+ certs_url (str): The URL that specifies the certificates to use to
+ verify the token. This URL should return JSON in the format of
+ ``{'key id': 'x509 certificate'}``.
+
+ Returns:
+ Mapping[str, Any]: The decoded token.
+ """
+ certs = _fetch_certs(request, certs_url)
+
+ return jwt.decode(id_token, certs=certs, audience=audience)
+
+
+def verify_oauth2_token(id_token, request, audience=None):
+ """Verifies an ID Token issued by Google's OAuth 2.0 authorization server.
+
+ Args:
+ id_token (Union[str, bytes]): The encoded token.
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
+ audience (str): The audience that this token is intended for. This is
+ typically your application's OAuth 2.0 client ID. If None then the
+ audience is not verified.
+
+ Returns:
+ Mapping[str, Any]: The decoded token.
+
+ Raises:
+ exceptions.GoogleAuthError: If the issuer is invalid.
+ """
+ idinfo = verify_token(
+ id_token, request, audience=audience, certs_url=_GOOGLE_OAUTH2_CERTS_URL
+ )
+
+ if idinfo["iss"] not in _GOOGLE_ISSUERS:
+ raise exceptions.GoogleAuthError(
+ "Wrong issuer. 'iss' should be one of the following: {}".format(
+ _GOOGLE_ISSUERS
+ )
+ )
+
+ return idinfo
+
+
+def verify_firebase_token(id_token, request, audience=None):
+ """Verifies an ID Token issued by Firebase Authentication.
+
+ Args:
+ id_token (Union[str, bytes]): The encoded token.
+ request (google.auth.transport.Request): The object used to make
+ HTTP requests.
+ audience (str): The audience that this token is intended for. This is
+ typically your Firebase application ID. If None then the audience
+ is not verified.
+
+ Returns:
+ Mapping[str, Any]: The decoded token.
+ """
+ return verify_token(
+ id_token, request, audience=audience, certs_url=_GOOGLE_APIS_CERTS_URL
+ )
+
+
+def fetch_id_token(request, audience):
+ """Fetch the ID Token from the current environment.
+
+ This function acquires ID token from the environment in the following order.
+ See https://google.aip.dev/auth/4110.
+
+ 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
+ to the path of a valid service account JSON file, then ID token is
+ acquired using this service account credentials.
+ 2. If the application is running in Compute Engine, App Engine or Cloud Run,
+ then the ID token are obtained from the metadata server.
+ 3. If metadata server doesn't exist and no valid service account credentials
+ are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will
+ be raised.
+
+ Example::
+
+ import google.oauth2.id_token
+ import google.auth.transport.requests
+
+ request = google.auth.transport.requests.Request()
+ target_audience = "https://pubsub.googleapis.com"
+
+ id_token = google.oauth2.id_token.fetch_id_token(request, target_audience)
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ audience (str): The audience that this ID token is intended for.
+
+ Returns:
+ str: The ID token.
+
+ Raises:
+ ~google.auth.exceptions.DefaultCredentialsError:
+ If metadata server doesn't exist and no valid service account
+ credentials are found.
+ """
+ # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
+ # variable.
+ credentials_filename = os.environ.get(environment_vars.CREDENTIALS)
+ if credentials_filename:
+ if not (
+ os.path.exists(credentials_filename)
+ and os.path.isfile(credentials_filename)
+ ):
+ raise exceptions.DefaultCredentialsError(
+ "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid."
+ )
+
+ try:
+ with open(credentials_filename, "r") as f:
+ from google.oauth2 import service_account
+
+ info = json.load(f)
+ if info.get("type") == "service_account":
+ credentials = service_account.IDTokenCredentials.from_service_account_info(
+ info, target_audience=audience
+ )
+ credentials.refresh(request)
+ return credentials.token
+ except ValueError as caught_exc:
+ new_exc = exceptions.DefaultCredentialsError(
+ "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.",
+ caught_exc,
+ )
+ six.raise_from(new_exc, caught_exc)
+
+ # 2. Try to fetch ID token from metada server if it exists. The code works for GAE and
+ # Cloud Run metadata server as well.
+ try:
+ from google.auth import compute_engine
+ from google.auth.compute_engine import _metadata
+
+ if _metadata.ping(request):
+ credentials = compute_engine.IDTokenCredentials(
+ request, audience, use_metadata_identity_endpoint=True
+ )
+ credentials.refresh(request)
+ return credentials.token
+ except (ImportError, exceptions.TransportError):
+ pass
+
+ raise exceptions.DefaultCredentialsError(
+ "Neither metadata server or valid service account credentials are found."
+ )
diff --git a/contrib/python/google-auth/py2/google/oauth2/reauth.py b/contrib/python/google-auth/py2/google/oauth2/reauth.py
new file mode 100644
index 0000000000..d914fe9a7d
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/reauth.py
@@ -0,0 +1,341 @@
+# Copyright 2021 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.
+
+"""A module that provides functions for handling rapt authentication.
+
+Reauth is a process of obtaining additional authentication (such as password,
+security token, etc.) while refreshing OAuth 2.0 credentials for a user.
+
+Credentials that use the Reauth flow must have the reauth scope,
+``https://www.googleapis.com/auth/accounts.reauth``.
+
+This module provides a high-level function for executing the Reauth process,
+:func:`refresh_grant`, and lower-level helpers for doing the individual
+steps of the reauth process.
+
+Those steps are:
+
+1. Obtaining a list of challenges from the reauth server.
+2. Running through each challenge and sending the result back to the reauth
+ server.
+3. Refreshing the access token using the returned rapt token.
+"""
+
+import sys
+
+from six.moves import range
+
+from google.auth import exceptions
+from google.oauth2 import _client
+from google.oauth2 import challenges
+
+
+_REAUTH_SCOPE = "https://www.googleapis.com/auth/accounts.reauth"
+_REAUTH_API = "https://reauth.googleapis.com/v2/sessions"
+
+_REAUTH_NEEDED_ERROR = "invalid_grant"
+_REAUTH_NEEDED_ERROR_INVALID_RAPT = "invalid_rapt"
+_REAUTH_NEEDED_ERROR_RAPT_REQUIRED = "rapt_required"
+
+_AUTHENTICATED = "AUTHENTICATED"
+_CHALLENGE_REQUIRED = "CHALLENGE_REQUIRED"
+_CHALLENGE_PENDING = "CHALLENGE_PENDING"
+
+
+# Override this global variable to set custom max number of rounds of reauth
+# challenges should be run.
+RUN_CHALLENGE_RETRY_LIMIT = 5
+
+
+def is_interactive():
+ """Check if we are in an interractive environment.
+
+ Override this function with a different logic if you are using this library
+ outside a CLI.
+
+ If the rapt token needs refreshing, the user needs to answer the challenges.
+ If the user is not in an interractive environment, the challenges can not
+ be answered and we just wait for timeout for no reason.
+
+ Returns:
+ bool: True if is interactive environment, False otherwise.
+ """
+
+ return sys.stdin.isatty()
+
+
+def _get_challenges(
+ request, supported_challenge_types, access_token, requested_scopes=None
+):
+ """Does initial request to reauth API to get the challenges.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ supported_challenge_types (Sequence[str]): list of challenge names
+ supported by the manager.
+ access_token (str): Access token with reauth scopes.
+ requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials.
+
+ Returns:
+ dict: The response from the reauth API.
+ """
+ body = {"supportedChallengeTypes": supported_challenge_types}
+ if requested_scopes:
+ body["oauthScopesForDomainPolicyLookup"] = requested_scopes
+
+ return _client._token_endpoint_request(
+ request, _REAUTH_API + ":start", body, access_token=access_token, use_json=True
+ )
+
+
+def _send_challenge_result(
+ request, session_id, challenge_id, client_input, access_token
+):
+ """Attempt to refresh access token by sending next challenge result.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ session_id (str): session id returned by the initial reauth call.
+ challenge_id (str): challenge id returned by the initial reauth call.
+ client_input: dict with a challenge-specific client input. For example:
+ ``{'credential': password}`` for password challenge.
+ access_token (str): Access token with reauth scopes.
+
+ Returns:
+ dict: The response from the reauth API.
+ """
+ body = {
+ "sessionId": session_id,
+ "challengeId": challenge_id,
+ "action": "RESPOND",
+ "proposalResponse": client_input,
+ }
+
+ return _client._token_endpoint_request(
+ request,
+ _REAUTH_API + "/{}:continue".format(session_id),
+ body,
+ access_token=access_token,
+ use_json=True,
+ )
+
+
+def _run_next_challenge(msg, request, access_token):
+ """Get the next challenge from msg and run it.
+
+ Args:
+ msg (dict): Reauth API response body (either from the initial request to
+ https://reauth.googleapis.com/v2/sessions:start or from sending the
+ previous challenge response to
+ https://reauth.googleapis.com/v2/sessions/id:continue)
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ access_token (str): reauth access token
+
+ Returns:
+ dict: The response from the reauth API.
+
+ Raises:
+ google.auth.exceptions.ReauthError: if reauth failed.
+ """
+ for challenge in msg["challenges"]:
+ if challenge["status"] != "READY":
+ # Skip non-activated challenges.
+ continue
+ c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None)
+ if not c:
+ raise exceptions.ReauthFailError(
+ "Unsupported challenge type {0}. Supported types: {1}".format(
+ challenge["challengeType"],
+ ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())),
+ )
+ )
+ if not c.is_locally_eligible:
+ raise exceptions.ReauthFailError(
+ "Challenge {0} is not locally eligible".format(
+ challenge["challengeType"]
+ )
+ )
+ client_input = c.obtain_challenge_input(challenge)
+ if not client_input:
+ return None
+ return _send_challenge_result(
+ request,
+ msg["sessionId"],
+ challenge["challengeId"],
+ client_input,
+ access_token,
+ )
+ return None
+
+
+def _obtain_rapt(request, access_token, requested_scopes):
+ """Given an http request method and reauth access token, get rapt token.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ access_token (str): reauth access token
+ requested_scopes (Sequence[str]): scopes required by the client application
+
+ Returns:
+ str: The rapt token.
+
+ Raises:
+ google.auth.exceptions.ReauthError: if reauth failed
+ """
+ msg = _get_challenges(
+ request,
+ list(challenges.AVAILABLE_CHALLENGES.keys()),
+ access_token,
+ requested_scopes,
+ )
+
+ if msg["status"] == _AUTHENTICATED:
+ return msg["encodedProofOfReauthToken"]
+
+ for _ in range(0, RUN_CHALLENGE_RETRY_LIMIT):
+ if not (
+ msg["status"] == _CHALLENGE_REQUIRED or msg["status"] == _CHALLENGE_PENDING
+ ):
+ raise exceptions.ReauthFailError(
+ "Reauthentication challenge failed due to API error: {}".format(
+ msg["status"]
+ )
+ )
+
+ if not is_interactive():
+ raise exceptions.ReauthFailError(
+ "Reauthentication challenge could not be answered because you are not"
+ " in an interactive session."
+ )
+
+ msg = _run_next_challenge(msg, request, access_token)
+
+ if msg["status"] == _AUTHENTICATED:
+ return msg["encodedProofOfReauthToken"]
+
+ # If we got here it means we didn't get authenticated.
+ raise exceptions.ReauthFailError("Failed to obtain rapt token.")
+
+
+def get_rapt_token(
+ request, client_id, client_secret, refresh_token, token_uri, scopes=None
+):
+ """Given an http request method and refresh_token, get rapt token.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ client_id (str): client id to get access token for reauth scope.
+ client_secret (str): client secret for the client_id
+ refresh_token (str): refresh token to refresh access token
+ token_uri (str): uri to refresh access token
+ scopes (Optional(Sequence[str])): scopes required by the client application
+
+ Returns:
+ str: The rapt token.
+ Raises:
+ google.auth.exceptions.RefreshError: If reauth failed.
+ """
+ sys.stderr.write("Reauthentication required.\n")
+
+ # Get access token for reauth.
+ access_token, _, _, _ = _client.refresh_grant(
+ request=request,
+ client_id=client_id,
+ client_secret=client_secret,
+ refresh_token=refresh_token,
+ token_uri=token_uri,
+ scopes=[_REAUTH_SCOPE],
+ )
+
+ # Get rapt token from reauth API.
+ rapt_token = _obtain_rapt(request, access_token, requested_scopes=scopes)
+
+ return rapt_token
+
+
+def refresh_grant(
+ request,
+ token_uri,
+ refresh_token,
+ client_id,
+ client_secret,
+ scopes=None,
+ rapt_token=None,
+):
+ """Implements the reauthentication flow.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ token_uri (str): The OAuth 2.0 authorizations server's token endpoint
+ URI.
+ refresh_token (str): The refresh token to use to get a new access
+ token.
+ client_id (str): The OAuth 2.0 application's client ID.
+ client_secret (str): The Oauth 2.0 appliaction's client secret.
+ scopes (Optional(Sequence[str])): Scopes to request. If present, all
+ scopes must be authorized for the refresh token. Useful if refresh
+ token has a wild card scope (e.g.
+ 'https://www.googleapis.com/auth/any-api').
+ rapt_token (Optional(str)): The rapt token for reauth.
+
+ Returns:
+ Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The
+ access token, new refresh token, expiration, the additional data
+ returned by the token endpoint, and the rapt token.
+
+ Raises:
+ google.auth.exceptions.RefreshError: If the token endpoint returned
+ an error.
+ """
+ body = {
+ "grant_type": _client._REFRESH_GRANT_TYPE,
+ "client_id": client_id,
+ "client_secret": client_secret,
+ "refresh_token": refresh_token,
+ }
+ if scopes:
+ body["scope"] = " ".join(scopes)
+ if rapt_token:
+ body["rapt"] = rapt_token
+
+ response_status_ok, response_data = _client._token_endpoint_request_no_throw(
+ request, token_uri, body
+ )
+ if (
+ not response_status_ok
+ and response_data.get("error") == _REAUTH_NEEDED_ERROR
+ and (
+ response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_INVALID_RAPT
+ or response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_RAPT_REQUIRED
+ )
+ ):
+ rapt_token = get_rapt_token(
+ request, client_id, client_secret, refresh_token, token_uri, scopes=scopes
+ )
+ body["rapt"] = rapt_token
+ (response_status_ok, response_data) = _client._token_endpoint_request_no_throw(
+ request, token_uri, body
+ )
+
+ if not response_status_ok:
+ _client._handle_error_response(response_data)
+ return _client._handle_refresh_grant_response(response_data, refresh_token) + (
+ rapt_token,
+ )
diff --git a/contrib/python/google-auth/py2/google/oauth2/service_account.py b/contrib/python/google-auth/py2/google/oauth2/service_account.py
new file mode 100644
index 0000000000..8f18f26ea1
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/service_account.py
@@ -0,0 +1,685 @@
+# Copyright 2016 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.
+
+"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0
+
+This module implements the JWT Profile for OAuth 2.0 Authorization Grants
+as defined by `RFC 7523`_ with particular support for how this RFC is
+implemented in Google's infrastructure. Google refers to these credentials
+as *Service Accounts*.
+
+Service accounts are used for server-to-server communication, such as
+interactions between a web application server and a Google service. The
+service account belongs to your application instead of to an individual end
+user. In contrast to other OAuth 2.0 profiles, no users are involved and your
+application "acts" as the service account.
+
+Typically an application uses a service account when the application uses
+Google APIs to work with its own data rather than a user's data. For example,
+an application that uses Google Cloud Datastore for data persistence would use
+a service account to authenticate its calls to the Google Cloud Datastore API.
+However, an application that needs to access a user's Drive documents would
+use the normal OAuth 2.0 profile.
+
+Additionally, Google Apps domain administrators can grant service accounts
+`domain-wide delegation`_ authority to access user data on behalf of users in
+the domain.
+
+This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used
+in place of the usual authorization token returned during the standard
+OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as
+the acquired access token is used as the bearer token when making requests
+using these credentials.
+
+This profile differs from normal OAuth 2.0 profile because no user consent
+step is required. The use of the private key allows this profile to assert
+identity directly.
+
+This profile also differs from the :mod:`google.auth.jwt` authentication
+because the JWT credentials use the JWT directly as the bearer token. This
+profile instead only uses the JWT to obtain an OAuth 2.0 access token. The
+obtained OAuth 2.0 access token is used as the bearer token.
+
+Domain-wide delegation
+----------------------
+
+Domain-wide delegation allows a service account to access user data on
+behalf of any user in a Google Apps domain without consent from the user.
+For example, an application that uses the Google Calendar API to add events to
+the calendars of all users in a Google Apps domain would use a service account
+to access the Google Calendar API on behalf of users.
+
+The Google Apps administrator must explicitly authorize the service account to
+do this. This authorization step is referred to as "delegating domain-wide
+authority" to a service account.
+
+You can use domain-wise delegation by creating a set of credentials with a
+specific subject using :meth:`~Credentials.with_subject`.
+
+.. _RFC 7523: https://tools.ietf.org/html/rfc7523
+"""
+
+import copy
+import datetime
+
+from google.auth import _helpers
+from google.auth import _service_account_info
+from google.auth import credentials
+from google.auth import jwt
+from google.oauth2 import _client
+
+_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
+_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
+
+
+class Credentials(
+ credentials.Signing, credentials.Scoped, credentials.CredentialsWithQuotaProject
+):
+ """Service account credentials
+
+ Usually, you'll create these credentials with one of the helper
+ constructors. To create credentials using a Google service account
+ private key JSON file::
+
+ credentials = service_account.Credentials.from_service_account_file(
+ 'service-account.json')
+
+ Or if you already have the service account file loaded::
+
+ service_account_info = json.load(open('service_account.json'))
+ credentials = service_account.Credentials.from_service_account_info(
+ service_account_info)
+
+ Both helper methods pass on arguments to the constructor, so you can
+ specify additional scopes and a subject if necessary::
+
+ credentials = service_account.Credentials.from_service_account_file(
+ 'service-account.json',
+ scopes=['email'],
+ subject='user@example.com')
+
+ The credentials are considered immutable. If you want to modify the scopes
+ or the subject used for delegation, use :meth:`with_scopes` or
+ :meth:`with_subject`::
+
+ scoped_credentials = credentials.with_scopes(['email'])
+ delegated_credentials = credentials.with_subject(subject)
+
+ To add a quota project, use :meth:`with_quota_project`::
+
+ credentials = credentials.with_quota_project('myproject-123')
+ """
+
+ def __init__(
+ self,
+ signer,
+ service_account_email,
+ token_uri,
+ scopes=None,
+ default_scopes=None,
+ subject=None,
+ project_id=None,
+ quota_project_id=None,
+ additional_claims=None,
+ always_use_jwt_access=False,
+ ):
+ """
+ Args:
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
+ service_account_email (str): The service account's email.
+ scopes (Sequence[str]): User-defined scopes to request during the
+ authorization grant.
+ default_scopes (Sequence[str]): Default scopes passed by a
+ Google client library. Use 'scopes' for user-defined scopes.
+ token_uri (str): The OAuth 2.0 Token URI.
+ subject (str): For domain-wide delegation, the email address of the
+ user to for which to request delegated access.
+ project_id (str): Project ID associated with the service account
+ credential.
+ quota_project_id (Optional[str]): The project ID used for quota and
+ billing.
+ additional_claims (Mapping[str, str]): Any additional claims for
+ the JWT assertion used in the authorization grant.
+ always_use_jwt_access (Optional[bool]): Whether self signed JWT should
+ be always used.
+
+ .. note:: Typically one of the helper constructors
+ :meth:`from_service_account_file` or
+ :meth:`from_service_account_info` are used instead of calling the
+ constructor directly.
+ """
+ super(Credentials, self).__init__()
+
+ self._scopes = scopes
+ self._default_scopes = default_scopes
+ self._signer = signer
+ self._service_account_email = service_account_email
+ self._subject = subject
+ self._project_id = project_id
+ self._quota_project_id = quota_project_id
+ self._token_uri = token_uri
+ self._always_use_jwt_access = always_use_jwt_access
+
+ self._jwt_credentials = None
+
+ if additional_claims is not None:
+ self._additional_claims = additional_claims
+ else:
+ self._additional_claims = {}
+
+ @classmethod
+ def _from_signer_and_info(cls, signer, info, **kwargs):
+ """Creates a Credentials instance from a signer and service account
+ info.
+
+ Args:
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
+ info (Mapping[str, str]): The service account info.
+ kwargs: Additional arguments to pass to the constructor.
+
+ Returns:
+ google.auth.jwt.Credentials: The constructed credentials.
+
+ Raises:
+ ValueError: If the info is not in the expected format.
+ """
+ return cls(
+ signer,
+ service_account_email=info["client_email"],
+ token_uri=info["token_uri"],
+ project_id=info.get("project_id"),
+ **kwargs
+ )
+
+ @classmethod
+ def from_service_account_info(cls, info, **kwargs):
+ """Creates a Credentials instance from parsed service account info.
+
+ Args:
+ info (Mapping[str, str]): The service account info in Google
+ format.
+ kwargs: Additional arguments to pass to the constructor.
+
+ Returns:
+ google.auth.service_account.Credentials: The constructed
+ credentials.
+
+ Raises:
+ ValueError: If the info is not in the expected format.
+ """
+ signer = _service_account_info.from_dict(
+ info, require=["client_email", "token_uri"]
+ )
+ return cls._from_signer_and_info(signer, info, **kwargs)
+
+ @classmethod
+ def from_service_account_file(cls, filename, **kwargs):
+ """Creates a Credentials instance from a service account json file.
+
+ Args:
+ filename (str): The path to the service account json file.
+ kwargs: Additional arguments to pass to the constructor.
+
+ Returns:
+ google.auth.service_account.Credentials: The constructed
+ credentials.
+ """
+ info, signer = _service_account_info.from_filename(
+ filename, require=["client_email", "token_uri"]
+ )
+ return cls._from_signer_and_info(signer, info, **kwargs)
+
+ @property
+ def service_account_email(self):
+ """The service account email."""
+ return self._service_account_email
+
+ @property
+ def project_id(self):
+ """Project ID associated with this credential."""
+ return self._project_id
+
+ @property
+ def requires_scopes(self):
+ """Checks if the credentials requires scopes.
+
+ Returns:
+ bool: True if there are no scopes set otherwise False.
+ """
+ return True if not self._scopes else False
+
+ @_helpers.copy_docstring(credentials.Scoped)
+ def with_scopes(self, scopes, default_scopes=None):
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ scopes=scopes,
+ default_scopes=default_scopes,
+ token_uri=self._token_uri,
+ subject=self._subject,
+ project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
+ additional_claims=self._additional_claims.copy(),
+ always_use_jwt_access=self._always_use_jwt_access,
+ )
+
+ def with_always_use_jwt_access(self, always_use_jwt_access):
+ """Create a copy of these credentials with the specified always_use_jwt_access value.
+
+ Args:
+ always_use_jwt_access (bool): Whether always use self signed JWT or not.
+
+ Returns:
+ google.auth.service_account.Credentials: A new credentials
+ instance.
+ """
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ scopes=self._scopes,
+ default_scopes=self._default_scopes,
+ token_uri=self._token_uri,
+ subject=self._subject,
+ project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
+ additional_claims=self._additional_claims.copy(),
+ always_use_jwt_access=always_use_jwt_access,
+ )
+
+ def with_subject(self, subject):
+ """Create a copy of these credentials with the specified subject.
+
+ Args:
+ subject (str): The subject claim.
+
+ Returns:
+ google.auth.service_account.Credentials: A new credentials
+ instance.
+ """
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ scopes=self._scopes,
+ default_scopes=self._default_scopes,
+ token_uri=self._token_uri,
+ subject=subject,
+ project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
+ additional_claims=self._additional_claims.copy(),
+ always_use_jwt_access=self._always_use_jwt_access,
+ )
+
+ def with_claims(self, additional_claims):
+ """Returns a copy of these credentials with modified claims.
+
+ Args:
+ additional_claims (Mapping[str, str]): Any additional claims for
+ the JWT payload. This will be merged with the current
+ additional claims.
+
+ Returns:
+ google.auth.service_account.Credentials: A new credentials
+ instance.
+ """
+ new_additional_claims = copy.deepcopy(self._additional_claims)
+ new_additional_claims.update(additional_claims or {})
+
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ scopes=self._scopes,
+ default_scopes=self._default_scopes,
+ token_uri=self._token_uri,
+ subject=self._subject,
+ project_id=self._project_id,
+ quota_project_id=self._quota_project_id,
+ additional_claims=new_additional_claims,
+ always_use_jwt_access=self._always_use_jwt_access,
+ )
+
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
+ def with_quota_project(self, quota_project_id):
+
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ default_scopes=self._default_scopes,
+ scopes=self._scopes,
+ token_uri=self._token_uri,
+ subject=self._subject,
+ project_id=self._project_id,
+ quota_project_id=quota_project_id,
+ additional_claims=self._additional_claims.copy(),
+ always_use_jwt_access=self._always_use_jwt_access,
+ )
+
+ def _make_authorization_grant_assertion(self):
+ """Create the OAuth 2.0 assertion.
+
+ This assertion is used during the OAuth 2.0 grant to acquire an
+ access token.
+
+ Returns:
+ bytes: The authorization grant assertion.
+ """
+ now = _helpers.utcnow()
+ lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
+ expiry = now + lifetime
+
+ payload = {
+ "iat": _helpers.datetime_to_secs(now),
+ "exp": _helpers.datetime_to_secs(expiry),
+ # The issuer must be the service account email.
+ "iss": self._service_account_email,
+ # The audience must be the auth token endpoint's URI
+ "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
+ "scope": _helpers.scopes_to_string(self._scopes or ()),
+ }
+
+ payload.update(self._additional_claims)
+
+ # The subject can be a user email for domain-wide delegation.
+ if self._subject:
+ payload.setdefault("sub", self._subject)
+
+ token = jwt.encode(self._signer, payload)
+
+ return token
+
+ @_helpers.copy_docstring(credentials.Credentials)
+ def refresh(self, request):
+ if self._jwt_credentials is not None:
+ self._jwt_credentials.refresh(request)
+ self.token = self._jwt_credentials.token
+ self.expiry = self._jwt_credentials.expiry
+ else:
+ assertion = self._make_authorization_grant_assertion()
+ access_token, expiry, _ = _client.jwt_grant(
+ request, self._token_uri, assertion
+ )
+ self.token = access_token
+ self.expiry = expiry
+
+ def _create_self_signed_jwt(self, audience):
+ """Create a self-signed JWT from the credentials if requirements are met.
+
+ Args:
+ audience (str): The service URL. ``https://[API_ENDPOINT]/``
+ """
+ # https://google.aip.dev/auth/4111
+ if self._always_use_jwt_access:
+ if self._scopes:
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
+ self, None, additional_claims={"scope": " ".join(self._scopes)}
+ )
+ elif audience:
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
+ self, audience
+ )
+ elif self._default_scopes:
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
+ self,
+ None,
+ additional_claims={"scope": " ".join(self._default_scopes)},
+ )
+ elif not self._scopes and audience:
+ self._jwt_credentials = jwt.Credentials.from_signing_credentials(
+ self, audience
+ )
+
+ @_helpers.copy_docstring(credentials.Signing)
+ def sign_bytes(self, message):
+ return self._signer.sign(message)
+
+ @property
+ @_helpers.copy_docstring(credentials.Signing)
+ def signer(self):
+ return self._signer
+
+ @property
+ @_helpers.copy_docstring(credentials.Signing)
+ def signer_email(self):
+ return self._service_account_email
+
+
+class IDTokenCredentials(credentials.Signing, credentials.CredentialsWithQuotaProject):
+ """Open ID Connect ID Token-based service account credentials.
+
+ These credentials are largely similar to :class:`.Credentials`, but instead
+ of using an OAuth 2.0 Access Token as the bearer token, they use an Open
+ ID Connect ID Token as the bearer token. These credentials are useful when
+ communicating to services that require ID Tokens and can not accept access
+ tokens.
+
+ Usually, you'll create these credentials with one of the helper
+ constructors. To create credentials using a Google service account
+ private key JSON file::
+
+ credentials = (
+ service_account.IDTokenCredentials.from_service_account_file(
+ 'service-account.json'))
+
+
+ Or if you already have the service account file loaded::
+
+ service_account_info = json.load(open('service_account.json'))
+ credentials = (
+ service_account.IDTokenCredentials.from_service_account_info(
+ service_account_info))
+
+
+ Both helper methods pass on arguments to the constructor, so you can
+ specify additional scopes and a subject if necessary::
+
+ credentials = (
+ service_account.IDTokenCredentials.from_service_account_file(
+ 'service-account.json',
+ scopes=['email'],
+ subject='user@example.com'))
+
+
+ The credentials are considered immutable. If you want to modify the scopes
+ or the subject used for delegation, use :meth:`with_scopes` or
+ :meth:`with_subject`::
+
+ scoped_credentials = credentials.with_scopes(['email'])
+ delegated_credentials = credentials.with_subject(subject)
+
+ """
+
+ def __init__(
+ self,
+ signer,
+ service_account_email,
+ token_uri,
+ target_audience,
+ additional_claims=None,
+ quota_project_id=None,
+ ):
+ """
+ Args:
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
+ service_account_email (str): The service account's email.
+ token_uri (str): The OAuth 2.0 Token URI.
+ target_audience (str): The intended audience for these credentials,
+ used when requesting the ID Token. The ID Token's ``aud`` claim
+ will be set to this string.
+ additional_claims (Mapping[str, str]): Any additional claims for
+ the JWT assertion used in the authorization grant.
+ quota_project_id (Optional[str]): The project ID used for quota and billing.
+ .. note:: Typically one of the helper constructors
+ :meth:`from_service_account_file` or
+ :meth:`from_service_account_info` are used instead of calling the
+ constructor directly.
+ """
+ super(IDTokenCredentials, self).__init__()
+ self._signer = signer
+ self._service_account_email = service_account_email
+ self._token_uri = token_uri
+ self._target_audience = target_audience
+ self._quota_project_id = quota_project_id
+
+ if additional_claims is not None:
+ self._additional_claims = additional_claims
+ else:
+ self._additional_claims = {}
+
+ @classmethod
+ def _from_signer_and_info(cls, signer, info, **kwargs):
+ """Creates a credentials instance from a signer and service account
+ info.
+
+ Args:
+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
+ info (Mapping[str, str]): The service account info.
+ kwargs: Additional arguments to pass to the constructor.
+
+ Returns:
+ google.auth.jwt.IDTokenCredentials: The constructed credentials.
+
+ Raises:
+ ValueError: If the info is not in the expected format.
+ """
+ kwargs.setdefault("service_account_email", info["client_email"])
+ kwargs.setdefault("token_uri", info["token_uri"])
+ return cls(signer, **kwargs)
+
+ @classmethod
+ def from_service_account_info(cls, info, **kwargs):
+ """Creates a credentials instance from parsed service account info.
+
+ Args:
+ info (Mapping[str, str]): The service account info in Google
+ format.
+ kwargs: Additional arguments to pass to the constructor.
+
+ Returns:
+ google.auth.service_account.IDTokenCredentials: The constructed
+ credentials.
+
+ Raises:
+ ValueError: If the info is not in the expected format.
+ """
+ signer = _service_account_info.from_dict(
+ info, require=["client_email", "token_uri"]
+ )
+ return cls._from_signer_and_info(signer, info, **kwargs)
+
+ @classmethod
+ def from_service_account_file(cls, filename, **kwargs):
+ """Creates a credentials instance from a service account json file.
+
+ Args:
+ filename (str): The path to the service account json file.
+ kwargs: Additional arguments to pass to the constructor.
+
+ Returns:
+ google.auth.service_account.IDTokenCredentials: The constructed
+ credentials.
+ """
+ info, signer = _service_account_info.from_filename(
+ filename, require=["client_email", "token_uri"]
+ )
+ return cls._from_signer_and_info(signer, info, **kwargs)
+
+ def with_target_audience(self, target_audience):
+ """Create a copy of these credentials with the specified target
+ audience.
+
+ Args:
+ target_audience (str): The intended audience for these credentials,
+ used when requesting the ID Token.
+
+ Returns:
+ google.auth.service_account.IDTokenCredentials: A new credentials
+ instance.
+ """
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ token_uri=self._token_uri,
+ target_audience=target_audience,
+ additional_claims=self._additional_claims.copy(),
+ quota_project_id=self.quota_project_id,
+ )
+
+ @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
+ def with_quota_project(self, quota_project_id):
+ return self.__class__(
+ self._signer,
+ service_account_email=self._service_account_email,
+ token_uri=self._token_uri,
+ target_audience=self._target_audience,
+ additional_claims=self._additional_claims.copy(),
+ quota_project_id=quota_project_id,
+ )
+
+ def _make_authorization_grant_assertion(self):
+ """Create the OAuth 2.0 assertion.
+
+ This assertion is used during the OAuth 2.0 grant to acquire an
+ ID token.
+
+ Returns:
+ bytes: The authorization grant assertion.
+ """
+ now = _helpers.utcnow()
+ lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
+ expiry = now + lifetime
+
+ payload = {
+ "iat": _helpers.datetime_to_secs(now),
+ "exp": _helpers.datetime_to_secs(expiry),
+ # The issuer must be the service account email.
+ "iss": self.service_account_email,
+ # The audience must be the auth token endpoint's URI
+ "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
+ # The target audience specifies which service the ID token is
+ # intended for.
+ "target_audience": self._target_audience,
+ }
+
+ payload.update(self._additional_claims)
+
+ token = jwt.encode(self._signer, payload)
+
+ return token
+
+ @_helpers.copy_docstring(credentials.Credentials)
+ def refresh(self, request):
+ assertion = self._make_authorization_grant_assertion()
+ access_token, expiry, _ = _client.id_token_jwt_grant(
+ request, self._token_uri, assertion
+ )
+ self.token = access_token
+ self.expiry = expiry
+
+ @property
+ def service_account_email(self):
+ """The service account email."""
+ return self._service_account_email
+
+ @_helpers.copy_docstring(credentials.Signing)
+ def sign_bytes(self, message):
+ return self._signer.sign(message)
+
+ @property
+ @_helpers.copy_docstring(credentials.Signing)
+ def signer(self):
+ return self._signer
+
+ @property
+ @_helpers.copy_docstring(credentials.Signing)
+ def signer_email(self):
+ return self._service_account_email
diff --git a/contrib/python/google-auth/py2/google/oauth2/sts.py b/contrib/python/google-auth/py2/google/oauth2/sts.py
new file mode 100644
index 0000000000..ae3c0146b1
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/sts.py
@@ -0,0 +1,155 @@
+# Copyright 2020 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.
+
+"""OAuth 2.0 Token Exchange Spec.
+
+This module defines a token exchange utility based on the `OAuth 2.0 Token
+Exchange`_ spec. This will be mainly used to exchange external credentials
+for GCP access tokens in workload identity pools to access Google APIs.
+
+The implementation will support various types of client authentication as
+allowed in the spec.
+
+A deviation on the spec will be for additional Google specific options that
+cannot be easily mapped to parameters defined in the RFC.
+
+The returned dictionary response will be based on the `rfc8693 section 2.2.1`_
+spec JSON response.
+
+.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693
+.. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1
+"""
+
+import json
+
+from six.moves import http_client
+from six.moves import urllib
+
+from google.oauth2 import utils
+
+
+_URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"}
+
+
+class Client(utils.OAuthClientAuthHandler):
+ """Implements the OAuth 2.0 token exchange spec based on
+ https://tools.ietf.org/html/rfc8693.
+ """
+
+ def __init__(self, token_exchange_endpoint, client_authentication=None):
+ """Initializes an STS client instance.
+
+ Args:
+ token_exchange_endpoint (str): The token exchange endpoint.
+ client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)):
+ The optional OAuth client authentication credentials if available.
+ """
+ super(Client, self).__init__(client_authentication)
+ self._token_exchange_endpoint = token_exchange_endpoint
+
+ def exchange_token(
+ self,
+ request,
+ grant_type,
+ subject_token,
+ subject_token_type,
+ resource=None,
+ audience=None,
+ scopes=None,
+ requested_token_type=None,
+ actor_token=None,
+ actor_token_type=None,
+ additional_options=None,
+ additional_headers=None,
+ ):
+ """Exchanges the provided token for another type of token based on the
+ rfc8693 spec.
+
+ Args:
+ request (google.auth.transport.Request): A callable used to make
+ HTTP requests.
+ grant_type (str): The OAuth 2.0 token exchange grant type.
+ subject_token (str): The OAuth 2.0 token exchange subject token.
+ subject_token_type (str): The OAuth 2.0 token exchange subject token type.
+ resource (Optional[str]): The optional OAuth 2.0 token exchange resource field.
+ audience (Optional[str]): The optional OAuth 2.0 token exchange audience field.
+ scopes (Optional[Sequence[str]]): The optional list of scopes to use.
+ requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested
+ token type.
+ actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token.
+ actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type.
+ additional_options (Optional[Mapping[str, str]]): The optional additional
+ non-standard Google specific options.
+ additional_headers (Optional[Mapping[str, str]]): The optional additional
+ headers to pass to the token exchange endpoint.
+
+ Returns:
+ Mapping[str, str]: The token exchange JSON-decoded response data containing
+ the requested token and its expiration time.
+
+ Raises:
+ google.auth.exceptions.OAuthError: If the token endpoint returned
+ an error.
+ """
+ # Initialize request headers.
+ headers = _URLENCODED_HEADERS.copy()
+ # Inject additional headers.
+ if additional_headers:
+ for k, v in dict(additional_headers).items():
+ headers[k] = v
+ # Initialize request body.
+ request_body = {
+ "grant_type": grant_type,
+ "resource": resource,
+ "audience": audience,
+ "scope": " ".join(scopes or []),
+ "requested_token_type": requested_token_type,
+ "subject_token": subject_token,
+ "subject_token_type": subject_token_type,
+ "actor_token": actor_token,
+ "actor_token_type": actor_token_type,
+ "options": None,
+ }
+ # Add additional non-standard options.
+ if additional_options:
+ request_body["options"] = urllib.parse.quote(json.dumps(additional_options))
+ # Remove empty fields in request body.
+ for k, v in dict(request_body).items():
+ if v is None or v == "":
+ del request_body[k]
+ # Apply OAuth client authentication.
+ self.apply_client_authentication_options(headers, request_body)
+
+ # Execute request.
+ response = request(
+ url=self._token_exchange_endpoint,
+ method="POST",
+ headers=headers,
+ body=urllib.parse.urlencode(request_body).encode("utf-8"),
+ )
+
+ response_body = (
+ response.data.decode("utf-8")
+ if hasattr(response.data, "decode")
+ else response.data
+ )
+
+ # If non-200 response received, translate to OAuthError exception.
+ if response.status != http_client.OK:
+ utils.handle_error_response(response_body)
+
+ response_data = json.loads(response_body)
+
+ # Return successful response.
+ return response_data
diff --git a/contrib/python/google-auth/py2/google/oauth2/utils.py b/contrib/python/google-auth/py2/google/oauth2/utils.py
new file mode 100644
index 0000000000..593f03236e
--- /dev/null
+++ b/contrib/python/google-auth/py2/google/oauth2/utils.py
@@ -0,0 +1,171 @@
+# Copyright 2020 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.
+
+"""OAuth 2.0 Utilities.
+
+This module provides implementations for various OAuth 2.0 utilities.
+This includes `OAuth error handling`_ and
+`Client authentication for OAuth flows`_.
+
+OAuth error handling
+--------------------
+This will define interfaces for handling OAuth related error responses as
+stated in `RFC 6749 section 5.2`_.
+This will include a common function to convert these HTTP error responses to a
+:class:`google.auth.exceptions.OAuthError` exception.
+
+
+Client authentication for OAuth flows
+-------------------------------------
+We introduce an interface for defining client authentication credentials based
+on `RFC 6749 section 2.3.1`_. This will expose the following
+capabilities:
+
+ * Ability to support basic authentication via request header.
+ * Ability to support bearer token authentication via request header.
+ * Ability to support client ID / secret authentication via request body.
+
+.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1
+.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2
+"""
+
+import abc
+import base64
+import enum
+import json
+
+import six
+
+from google.auth import exceptions
+
+
+# OAuth client authentication based on
+# https://tools.ietf.org/html/rfc6749#section-2.3.
+class ClientAuthType(enum.Enum):
+ basic = 1
+ request_body = 2
+
+
+class ClientAuthentication(object):
+ """Defines the client authentication credentials for basic and request-body
+ types based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
+ """
+
+ def __init__(self, client_auth_type, client_id, client_secret=None):
+ """Instantiates a client authentication object containing the client ID
+ and secret credentials for basic and response-body auth.
+
+ Args:
+ client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The
+ client authentication type.
+ client_id (str): The client ID.
+ client_secret (Optional[str]): The client secret.
+ """
+ self.client_auth_type = client_auth_type
+ self.client_id = client_id
+ self.client_secret = client_secret
+
+
+@six.add_metaclass(abc.ABCMeta)
+class OAuthClientAuthHandler(object):
+ """Abstract class for handling client authentication in OAuth-based
+ operations.
+ """
+
+ def __init__(self, client_authentication=None):
+ """Instantiates an OAuth client authentication handler.
+
+ Args:
+ client_authentication (Optional[google.oauth2.utils.ClientAuthentication]):
+ The OAuth client authentication credentials if available.
+ """
+ super(OAuthClientAuthHandler, self).__init__()
+ self._client_authentication = client_authentication
+
+ def apply_client_authentication_options(
+ self, headers, request_body=None, bearer_token=None
+ ):
+ """Applies client authentication on the OAuth request's headers or POST
+ body.
+
+ Args:
+ headers (Mapping[str, str]): The HTTP request header.
+ request_body (Optional[Mapping[str, str]]): The HTTP request body
+ dictionary. For requests that do not support request body, this
+ is None and will be ignored.
+ bearer_token (Optional[str]): The optional bearer token.
+ """
+ # Inject authenticated header.
+ self._inject_authenticated_headers(headers, bearer_token)
+ # Inject authenticated request body.
+ if bearer_token is None:
+ self._inject_authenticated_request_body(request_body)
+
+ def _inject_authenticated_headers(self, headers, bearer_token=None):
+ if bearer_token is not None:
+ headers["Authorization"] = "Bearer %s" % bearer_token
+ elif (
+ self._client_authentication is not None
+ and self._client_authentication.client_auth_type is ClientAuthType.basic
+ ):
+ username = self._client_authentication.client_id
+ password = self._client_authentication.client_secret or ""
+
+ credentials = base64.b64encode(
+ ("%s:%s" % (username, password)).encode()
+ ).decode()
+ headers["Authorization"] = "Basic %s" % credentials
+
+ def _inject_authenticated_request_body(self, request_body):
+ if (
+ self._client_authentication is not None
+ and self._client_authentication.client_auth_type
+ is ClientAuthType.request_body
+ ):
+ if request_body is None:
+ raise exceptions.OAuthError(
+ "HTTP request does not support request-body"
+ )
+ else:
+ request_body["client_id"] = self._client_authentication.client_id
+ request_body["client_secret"] = (
+ self._client_authentication.client_secret or ""
+ )
+
+
+def handle_error_response(response_body):
+ """Translates an error response from an OAuth operation into an
+ OAuthError exception.
+
+ Args:
+ response_body (str): The decoded response data.
+
+ Raises:
+ google.auth.exceptions.OAuthError
+ """
+ try:
+ error_components = []
+ error_data = json.loads(response_body)
+
+ error_components.append("Error code {}".format(error_data["error"]))
+ if "error_description" in error_data:
+ error_components.append(": {}".format(error_data["error_description"]))
+ if "error_uri" in error_data:
+ error_components.append(" - {}".format(error_data["error_uri"]))
+ error_details = "".join(error_components)
+ # If no details could be extracted, use the response data.
+ except (KeyError, ValueError):
+ error_details = response_body
+
+ raise exceptions.OAuthError(error_details, response_body)