base.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. # -*- coding: utf-8 -*-
  2. """
  3. oauthlib.oauth1.rfc5849.endpoints.base
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. This module is an implementation of various logic needed
  6. for signing and checking OAuth 1.0 RFC 5849 requests.
  7. """
  8. from __future__ import absolute_import, unicode_literals
  9. import time
  10. from oauthlib.common import Request, generate_token
  11. from .. import signature, utils, errors
  12. from .. import CONTENT_TYPE_FORM_URLENCODED
  13. from .. import SIGNATURE_HMAC, SIGNATURE_RSA
  14. from .. import SIGNATURE_TYPE_AUTH_HEADER
  15. from .. import SIGNATURE_TYPE_QUERY
  16. from .. import SIGNATURE_TYPE_BODY
  17. class BaseEndpoint(object):
  18. def __init__(self, request_validator, token_generator=None):
  19. self.request_validator = request_validator
  20. self.token_generator = token_generator or generate_token
  21. def _get_signature_type_and_params(self, request):
  22. """Extracts parameters from query, headers and body. Signature type
  23. is set to the source in which parameters were found.
  24. """
  25. # Per RFC5849, only the Authorization header may contain the 'realm'
  26. # optional parameter.
  27. header_params = signature.collect_parameters(headers=request.headers,
  28. exclude_oauth_signature=False, with_realm=True)
  29. body_params = signature.collect_parameters(body=request.body,
  30. exclude_oauth_signature=False)
  31. query_params = signature.collect_parameters(uri_query=request.uri_query,
  32. exclude_oauth_signature=False)
  33. params = []
  34. params.extend(header_params)
  35. params.extend(body_params)
  36. params.extend(query_params)
  37. signature_types_with_oauth_params = list(filter(lambda s: s[2], (
  38. (SIGNATURE_TYPE_AUTH_HEADER, params,
  39. utils.filter_oauth_params(header_params)),
  40. (SIGNATURE_TYPE_BODY, params,
  41. utils.filter_oauth_params(body_params)),
  42. (SIGNATURE_TYPE_QUERY, params,
  43. utils.filter_oauth_params(query_params))
  44. )))
  45. if len(signature_types_with_oauth_params) > 1:
  46. found_types = [s[0] for s in signature_types_with_oauth_params]
  47. raise errors.InvalidRequestError(
  48. description=('oauth_ params must come from only 1 signature'
  49. 'type but were found in %s',
  50. ', '.join(found_types)))
  51. try:
  52. signature_type, params, oauth_params = signature_types_with_oauth_params[
  53. 0]
  54. except IndexError:
  55. raise errors.InvalidRequestError(
  56. description='Missing mandatory OAuth parameters.')
  57. return signature_type, params, oauth_params
  58. def _create_request(self, uri, http_method, body, headers):
  59. # Only include body data from x-www-form-urlencoded requests
  60. headers = headers or {}
  61. if ("Content-Type" in headers and
  62. CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]):
  63. request = Request(uri, http_method, body, headers)
  64. else:
  65. request = Request(uri, http_method, '', headers)
  66. signature_type, params, oauth_params = (
  67. self._get_signature_type_and_params(request))
  68. # The server SHOULD return a 400 (Bad Request) status code when
  69. # receiving a request with duplicated protocol parameters.
  70. if len(dict(oauth_params)) != len(oauth_params):
  71. raise errors.InvalidRequestError(
  72. description='Duplicate OAuth1 entries.')
  73. oauth_params = dict(oauth_params)
  74. request.signature = oauth_params.get('oauth_signature')
  75. request.client_key = oauth_params.get('oauth_consumer_key')
  76. request.resource_owner_key = oauth_params.get('oauth_token')
  77. request.nonce = oauth_params.get('oauth_nonce')
  78. request.timestamp = oauth_params.get('oauth_timestamp')
  79. request.redirect_uri = oauth_params.get('oauth_callback')
  80. request.verifier = oauth_params.get('oauth_verifier')
  81. request.signature_method = oauth_params.get('oauth_signature_method')
  82. request.realm = dict(params).get('realm')
  83. request.oauth_params = oauth_params
  84. # Parameters to Client depend on signature method which may vary
  85. # for each request. Note that HMAC-SHA1 and PLAINTEXT share parameters
  86. request.params = [(k, v) for k, v in params if k != "oauth_signature"]
  87. if 'realm' in request.headers.get('Authorization', ''):
  88. request.params = [(k, v)
  89. for k, v in request.params if k != "realm"]
  90. return request
  91. def _check_transport_security(self, request):
  92. # TODO: move into oauthlib.common from oauth2.utils
  93. if (self.request_validator.enforce_ssl and
  94. not request.uri.lower().startswith("https://")):
  95. raise errors.InsecureTransportError()
  96. def _check_mandatory_parameters(self, request):
  97. # The server SHOULD return a 400 (Bad Request) status code when
  98. # receiving a request with missing parameters.
  99. if not all((request.signature, request.client_key,
  100. request.nonce, request.timestamp,
  101. request.signature_method)):
  102. raise errors.InvalidRequestError(
  103. description='Missing mandatory OAuth parameters.')
  104. # OAuth does not mandate a particular signature method, as each
  105. # implementation can have its own unique requirements. Servers are
  106. # free to implement and document their own custom methods.
  107. # Recommending any particular method is beyond the scope of this
  108. # specification. Implementers should review the Security
  109. # Considerations section (`Section 4`_) before deciding on which
  110. # method to support.
  111. # .. _`Section 4`: http://tools.ietf.org/html/rfc5849#section-4
  112. if (not request.signature_method in
  113. self.request_validator.allowed_signature_methods):
  114. raise errors.InvalidSignatureMethodError(
  115. description="Invalid signature, %s not in %r." % (
  116. request.signature_method,
  117. self.request_validator.allowed_signature_methods))
  118. # Servers receiving an authenticated request MUST validate it by:
  119. # If the "oauth_version" parameter is present, ensuring its value is
  120. # "1.0".
  121. if ('oauth_version' in request.oauth_params and
  122. request.oauth_params['oauth_version'] != '1.0'):
  123. raise errors.InvalidRequestError(
  124. description='Invalid OAuth version.')
  125. # The timestamp value MUST be a positive integer. Unless otherwise
  126. # specified by the server's documentation, the timestamp is expressed
  127. # in the number of seconds since January 1, 1970 00:00:00 GMT.
  128. if len(request.timestamp) != 10:
  129. raise errors.InvalidRequestError(
  130. description='Invalid timestamp size')
  131. try:
  132. ts = int(request.timestamp)
  133. except ValueError:
  134. raise errors.InvalidRequestError(
  135. description='Timestamp must be an integer.')
  136. else:
  137. # To avoid the need to retain an infinite number of nonce values for
  138. # future checks, servers MAY choose to restrict the time period after
  139. # which a request with an old timestamp is rejected.
  140. if abs(time.time() - ts) > self.request_validator.timestamp_lifetime:
  141. raise errors.InvalidRequestError(
  142. description=('Timestamp given is invalid, differ from '
  143. 'allowed by over %s seconds.' % (
  144. self.request_validator.timestamp_lifetime)))
  145. # Provider specific validation of parameters, used to enforce
  146. # restrictions such as character set and length.
  147. if not self.request_validator.check_client_key(request.client_key):
  148. raise errors.InvalidRequestError(
  149. description='Invalid client key format.')
  150. if not self.request_validator.check_nonce(request.nonce):
  151. raise errors.InvalidRequestError(
  152. description='Invalid nonce format.')
  153. def _check_signature(self, request, is_token_request=False):
  154. # ---- RSA Signature verification ----
  155. if request.signature_method == SIGNATURE_RSA:
  156. # The server verifies the signature per `[RFC3447] section 8.2.2`_
  157. # .. _`[RFC3447] section 8.2.2`: http://tools.ietf.org/html/rfc3447#section-8.2.1
  158. rsa_key = self.request_validator.get_rsa_key(
  159. request.client_key, request)
  160. valid_signature = signature.verify_rsa_sha1(request, rsa_key)
  161. # ---- HMAC or Plaintext Signature verification ----
  162. else:
  163. # Servers receiving an authenticated request MUST validate it by:
  164. # Recalculating the request signature independently as described in
  165. # `Section 3.4`_ and comparing it to the value received from the
  166. # client via the "oauth_signature" parameter.
  167. # .. _`Section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
  168. client_secret = self.request_validator.get_client_secret(
  169. request.client_key, request)
  170. resource_owner_secret = None
  171. if request.resource_owner_key:
  172. if is_token_request:
  173. resource_owner_secret = self.request_validator.get_request_token_secret(
  174. request.client_key, request.resource_owner_key, request)
  175. else:
  176. resource_owner_secret = self.request_validator.get_access_token_secret(
  177. request.client_key, request.resource_owner_key, request)
  178. if request.signature_method == SIGNATURE_HMAC:
  179. valid_signature = signature.verify_hmac_sha1(request,
  180. client_secret, resource_owner_secret)
  181. else:
  182. valid_signature = signature.verify_plaintext(request,
  183. client_secret, resource_owner_secret)
  184. return valid_signature