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

Benjamin Mako Hill || Want to submit a patch?