blob: efda7968dbaa4aa5b5bd9d2f13a77804613f49c2 [file] [log] [blame]
bojeil-googled4d7f382021-02-16 12:33:20 -08001# Copyright 2020 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""OAuth 2.0 Utilities.
16
17This module provides implementations for various OAuth 2.0 utilities.
18This includes `OAuth error handling`_ and
19`Client authentication for OAuth flows`_.
20
21OAuth error handling
22--------------------
23This will define interfaces for handling OAuth related error responses as
24stated in `RFC 6749 section 5.2`_.
25This will include a common function to convert these HTTP error responses to a
26:class:`google.auth.exceptions.OAuthError` exception.
27
28
29Client authentication for OAuth flows
30-------------------------------------
31We introduce an interface for defining client authentication credentials based
32on `RFC 6749 section 2.3.1`_. This will expose the following
33capabilities:
34
35 * Ability to support basic authentication via request header.
36 * Ability to support bearer token authentication via request header.
37 * Ability to support client ID / secret authentication via request body.
38
39.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1
40.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2
41"""
42
43import abc
44import base64
45import enum
46import json
47
48import six
49
50from google.auth import exceptions
51
52
53# OAuth client authentication based on
54# https://tools.ietf.org/html/rfc6749#section-2.3.
55class ClientAuthType(enum.Enum):
56 basic = 1
57 request_body = 2
58
59
60class ClientAuthentication(object):
61 """Defines the client authentication credentials for basic and request-body
62 types based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
63 """
64
65 def __init__(self, client_auth_type, client_id, client_secret=None):
66 """Instantiates a client authentication object containing the client ID
67 and secret credentials for basic and response-body auth.
68
69 Args:
70 client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The
71 client authentication type.
72 client_id (str): The client ID.
73 client_secret (Optional[str]): The client secret.
74 """
75 self.client_auth_type = client_auth_type
76 self.client_id = client_id
77 self.client_secret = client_secret
78
79
80@six.add_metaclass(abc.ABCMeta)
81class OAuthClientAuthHandler(object):
82 """Abstract class for handling client authentication in OAuth-based
83 operations.
84 """
85
86 def __init__(self, client_authentication=None):
87 """Instantiates an OAuth client authentication handler.
88
89 Args:
90 client_authentication (Optional[google.oauth2.utils.ClientAuthentication]):
91 The OAuth client authentication credentials if available.
92 """
93 super(OAuthClientAuthHandler, self).__init__()
94 self._client_authentication = client_authentication
95
96 def apply_client_authentication_options(
97 self, headers, request_body=None, bearer_token=None
98 ):
99 """Applies client authentication on the OAuth request's headers or POST
100 body.
101
102 Args:
103 headers (Mapping[str, str]): The HTTP request header.
104 request_body (Optional[Mapping[str, str]): The HTTP request body
105 dictionary. For requests that do not support request body, this
106 is None and will be ignored.
107 bearer_token (Optional[str]): The optional bearer token.
108 """
109 # Inject authenticated header.
110 self._inject_authenticated_headers(headers, bearer_token)
111 # Inject authenticated request body.
112 if bearer_token is None:
113 self._inject_authenticated_request_body(request_body)
114
115 def _inject_authenticated_headers(self, headers, bearer_token=None):
116 if bearer_token is not None:
117 headers["Authorization"] = "Bearer %s" % bearer_token
118 elif (
119 self._client_authentication is not None
120 and self._client_authentication.client_auth_type is ClientAuthType.basic
121 ):
122 username = self._client_authentication.client_id
123 password = self._client_authentication.client_secret or ""
124
125 credentials = base64.b64encode(
126 ("%s:%s" % (username, password)).encode()
127 ).decode()
128 headers["Authorization"] = "Basic %s" % credentials
129
130 def _inject_authenticated_request_body(self, request_body):
131 if (
132 self._client_authentication is not None
133 and self._client_authentication.client_auth_type
134 is ClientAuthType.request_body
135 ):
136 if request_body is None:
137 raise exceptions.OAuthError(
138 "HTTP request does not support request-body"
139 )
140 else:
141 request_body["client_id"] = self._client_authentication.client_id
142 request_body["client_secret"] = (
143 self._client_authentication.client_secret or ""
144 )
145
146
147def handle_error_response(response_body):
148 """Translates an error response from an OAuth operation into an
149 OAuthError exception.
150
151 Args:
152 response_body (str): The decoded response data.
153
154 Raises:
155 google.auth.exceptions.OAuthError
156 """
157 try:
158 error_components = []
159 error_data = json.loads(response_body)
160
161 error_components.append("Error code {}".format(error_data["error"]))
162 if "error_description" in error_data:
163 error_components.append(": {}".format(error_data["error_description"]))
164 if "error_uri" in error_data:
165 error_components.append(" - {}".format(error_data["error_uri"]))
166 error_details = "".join(error_components)
167 # If no details could be extracted, use the response data.
168 except (KeyError, ValueError):
169 error_details = response_body
170
171 raise exceptions.OAuthError(error_details, response_body)