feat: workload identity federation support (#698)

Using workload identity federation, applications can access Google Cloud resources from Amazon Web Services (AWS), Microsoft Azure or any identity provider that supports OpenID Connect (OIDC). Workload identity federation is recommended for non-Google Cloud environments as it avoids the need to download, manage and store service account private keys locally.

This includes a rollforward of the [previous reverted PR](https://github.com/googleapis/google-auth-library-python/pull/686) and the [fix](https://github.com/googleapis/google-auth-library-python/pull/686) to not pass scopes to user credentials from `google.auth.default()`.
diff --git a/google/oauth2/utils.py b/google/oauth2/utils.py
new file mode 100644
index 0000000..efda796
--- /dev/null
+++ b/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)