utils.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. import six
  40. from google.auth import exceptions
  41. # OAuth client authentication based on
  42. # https://tools.ietf.org/html/rfc6749#section-2.3.
  43. class ClientAuthType(enum.Enum):
  44. basic = 1
  45. request_body = 2
  46. class ClientAuthentication(object):
  47. """Defines the client authentication credentials for basic and request-body
  48. types based on https://tools.ietf.org/html/rfc6749#section-2.3.1.
  49. """
  50. def __init__(self, client_auth_type, client_id, client_secret=None):
  51. """Instantiates a client authentication object containing the client ID
  52. and secret credentials for basic and response-body auth.
  53. Args:
  54. client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The
  55. client authentication type.
  56. client_id (str): The client ID.
  57. client_secret (Optional[str]): The client secret.
  58. """
  59. self.client_auth_type = client_auth_type
  60. self.client_id = client_id
  61. self.client_secret = client_secret
  62. @six.add_metaclass(abc.ABCMeta)
  63. class OAuthClientAuthHandler(object):
  64. """Abstract class for handling client authentication in OAuth-based
  65. operations.
  66. """
  67. def __init__(self, client_authentication=None):
  68. """Instantiates an OAuth client authentication handler.
  69. Args:
  70. client_authentication (Optional[google.oauth2.utils.ClientAuthentication]):
  71. The OAuth client authentication credentials if available.
  72. """
  73. super(OAuthClientAuthHandler, self).__init__()
  74. self._client_authentication = client_authentication
  75. def apply_client_authentication_options(
  76. self, headers, request_body=None, bearer_token=None
  77. ):
  78. """Applies client authentication on the OAuth request's headers or POST
  79. body.
  80. Args:
  81. headers (Mapping[str, str]): The HTTP request header.
  82. request_body (Optional[Mapping[str, str]]): The HTTP request body
  83. dictionary. For requests that do not support request body, this
  84. is None and will be ignored.
  85. bearer_token (Optional[str]): The optional bearer token.
  86. """
  87. # Inject authenticated header.
  88. self._inject_authenticated_headers(headers, bearer_token)
  89. # Inject authenticated request body.
  90. if bearer_token is None:
  91. self._inject_authenticated_request_body(request_body)
  92. def _inject_authenticated_headers(self, headers, bearer_token=None):
  93. if bearer_token is not None:
  94. headers["Authorization"] = "Bearer %s" % bearer_token
  95. elif (
  96. self._client_authentication is not None
  97. and self._client_authentication.client_auth_type is ClientAuthType.basic
  98. ):
  99. username = self._client_authentication.client_id
  100. password = self._client_authentication.client_secret or ""
  101. credentials = base64.b64encode(
  102. ("%s:%s" % (username, password)).encode()
  103. ).decode()
  104. headers["Authorization"] = "Basic %s" % credentials
  105. def _inject_authenticated_request_body(self, request_body):
  106. if (
  107. self._client_authentication is not None
  108. and self._client_authentication.client_auth_type
  109. is ClientAuthType.request_body
  110. ):
  111. if request_body is None:
  112. raise exceptions.OAuthError(
  113. "HTTP request does not support request-body"
  114. )
  115. else:
  116. request_body["client_id"] = self._client_authentication.client_id
  117. request_body["client_secret"] = (
  118. self._client_authentication.client_secret or ""
  119. )
  120. def handle_error_response(response_body):
  121. """Translates an error response from an OAuth operation into an
  122. OAuthError exception.
  123. Args:
  124. response_body (str): The decoded response data.
  125. Raises:
  126. google.auth.exceptions.OAuthError
  127. """
  128. try:
  129. error_components = []
  130. error_data = json.loads(response_body)
  131. error_components.append("Error code {}".format(error_data["error"]))
  132. if "error_description" in error_data:
  133. error_components.append(": {}".format(error_data["error_description"]))
  134. if "error_uri" in error_data:
  135. error_components.append(" - {}".format(error_data["error_uri"]))
  136. error_details = "".join(error_components)
  137. # If no details could be extracted, use the response data.
  138. except (KeyError, ValueError):
  139. error_details = response_body
  140. raise exceptions.OAuthError(error_details, response_body)