utils.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. """OAuth 2.0 Utilities.
  15. This module provides implementations for various OAuth 2.0 utilities.
  16. This includes `OAuth error handling`_ and
  17. `Client authentication for OAuth flows`_.
  18. OAuth error handling
  19. --------------------
  20. This will define interfaces for handling OAuth related error responses as
  21. stated in `RFC 6749 section 5.2`_.
  22. This will include a common function to convert these HTTP error responses to a
  23. :class:`google.auth.exceptions.OAuthError` exception.
  24. Client authentication for OAuth flows
  25. -------------------------------------
  26. We introduce an interface for defining client authentication credentials based
  27. on `RFC 6749 section 2.3.1`_. This will expose the following
  28. capabilities:
  29. * Ability to support basic authentication via request header.
  30. * Ability to support bearer token authentication via request header.
  31. * Ability to support client ID / secret authentication via request body.
  32. .. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1
  33. .. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2
  34. """
  35. import abc
  36. import base64
  37. import enum
  38. import json
  39. from google.auth import exceptions
  40. # OAuth client authentication based on
  41. # https://tools.ietf.org/html/rfc6749#section-2.3.
  42. class ClientAuthType(enum.Enum):
  43. basic = 1
  44. request_body = 2
  45. class ClientAuthentication(object):
  46. """Defines the client authentication credentials for basic and request-body
  47. types based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
  48. """
  49. def __init__(self, client_auth_type, client_id, client_secret=None):
  50. """Instantiates a client authentication object containing the client ID
  51. and secret credentials for basic and response-body auth.
  52. Args:
  53. client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The
  54. client authentication type.
  55. client_id (str): The client ID.
  56. client_secret (Optional[str]): The client secret.
  57. """
  58. self.client_auth_type = client_auth_type
  59. self.client_id = client_id
  60. self.client_secret = client_secret
  61. class OAuthClientAuthHandler(metaclass=abc.ABCMeta):
  62. """Abstract class for handling client authentication in OAuth-based
  63. operations.
  64. """
  65. def __init__(self, client_authentication=None):
  66. """Instantiates an OAuth client authentication handler.
  67. Args:
  68. client_authentication (Optional[google.oauth2.utils.ClientAuthentication]):
  69. The OAuth client authentication credentials if available.
  70. """
  71. super(OAuthClientAuthHandler, self).__init__()
  72. self._client_authentication = client_authentication
  73. def apply_client_authentication_options(
  74. self, headers, request_body=None, bearer_token=None
  75. ):
  76. """Applies client authentication on the OAuth request's headers or POST
  77. body.
  78. Args:
  79. headers (Mapping[str, str]): The HTTP request header.
  80. request_body (Optional[Mapping[str, str]]): The HTTP request body
  81. dictionary. For requests that do not support request body, this
  82. is None and will be ignored.
  83. bearer_token (Optional[str]): The optional bearer token.
  84. """
  85. # Inject authenticated header.
  86. self._inject_authenticated_headers(headers, bearer_token)
  87. # Inject authenticated request body.
  88. if bearer_token is None:
  89. self._inject_authenticated_request_body(request_body)
  90. def _inject_authenticated_headers(self, headers, bearer_token=None):
  91. if bearer_token is not None:
  92. headers["Authorization"] = "Bearer %s" % bearer_token
  93. elif (
  94. self._client_authentication is not None
  95. and self._client_authentication.client_auth_type is ClientAuthType.basic
  96. ):
  97. username = self._client_authentication.client_id
  98. password = self._client_authentication.client_secret or ""
  99. credentials = base64.b64encode(
  100. ("%s:%s" % (username, password)).encode()
  101. ).decode()
  102. headers["Authorization"] = "Basic %s" % credentials
  103. def _inject_authenticated_request_body(self, request_body):
  104. if (
  105. self._client_authentication is not None
  106. and self._client_authentication.client_auth_type
  107. is ClientAuthType.request_body
  108. ):
  109. if request_body is None:
  110. raise exceptions.OAuthError(
  111. "HTTP request does not support request-body"
  112. )
  113. else:
  114. request_body["client_id"] = self._client_authentication.client_id
  115. request_body["client_secret"] = (
  116. self._client_authentication.client_secret or ""
  117. )
  118. def handle_error_response(response_body):
  119. """Translates an error response from an OAuth operation into an
  120. OAuthError exception.
  121. Args:
  122. response_body (str): The decoded response data.
  123. Raises:
  124. google.auth.exceptions.OAuthError
  125. """
  126. try:
  127. error_components = []
  128. error_data = json.loads(response_body)
  129. error_components.append("Error code {}".format(error_data["error"]))
  130. if "error_description" in error_data:
  131. error_components.append(": {}".format(error_data["error_description"]))
  132. if "error_uri" in error_data:
  133. error_components.append(" - {}".format(error_data["error_uri"]))
  134. error_details = "".join(error_components)
  135. # If no details could be extracted, use the response data.
  136. except (KeyError, ValueError):
  137. error_details = response_body
  138. raise exceptions.OAuthError(error_details, response_body)