aboutsummaryrefslogtreecommitdiff
path: root/google/oauth2/utils.py
blob: 593f03236ec89dd808bd9e5ddd04ef9ca35cb328 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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)