1# 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) 172