Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions google/auth/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ class MutualTLSChannelError(GoogleAuthError):

class ClientCertError(GoogleAuthError):
"""Used to indicate that client certificate is missing or invalid."""


class OAuthError(GoogleAuthError):
"""Used to indicate an error occurred during an OAuth related HTTP
request."""
180 changes: 180 additions & 0 deletions google/oauth2/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# 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 json

import six

from google.auth import exceptions


def enum(**named_values):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use Python provided enums? https://cpython-test-docs.readthedocs.io/en/latest/library/enum.html

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I tried it out with the backport enum34 package . It seems to work locally.

"""Defines an enum utility.

Args:
named_values: The enum named values.

Returns:
type: The Enum metaclass.
"""
return type("Enum", (), named_values)


# OAuth client authentication based on
# https://tools.ietf.org/html/rfc6749#section-2.3.
ClientAuthType = enum(BASIC="basic", REQUEST_BODY="request-body")


class ClientAuthentication(object):
"""Defines the client authentication credentials for basic and request-body
types based on Based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
Comment thread
busunkim96 marked this conversation as resolved.
Outdated
"""

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)
Loading