Updated packages and code to python3. Won't work with python 2
[twitter-api-cdsw] / oauthlib / oauth1 / rfc5849 / endpoints / access_token.py
1 # -*- coding: utf-8 -*-
2 """
3 oauthlib.oauth1.rfc5849.endpoints.access_token
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 This module is an implementation of the access token provider logic of
7 OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
8 creates and persists tokens as well as create the proper response to be
9 returned to the client.
10 """
11 from __future__ import absolute_import, unicode_literals
12
13 import logging
14
15 from oauthlib.common import urlencode
16
17 from .base import BaseEndpoint
18 from .. import errors
19
20 log = logging.getLogger(__name__)
21
22
23 class AccessTokenEndpoint(BaseEndpoint):
24
25     """An endpoint responsible for providing OAuth 1 access tokens.
26
27     Typical use is to instantiate with a request validator and invoke the
28     ``create_access_token_response`` from a view function. The tuple returned
29     has all information necessary (body, status, headers) to quickly form
30     and return a proper response. See :doc:`/oauth1/validator` for details on which
31     validator methods to implement for this endpoint.
32     """
33
34     def create_access_token(self, request, credentials):
35         """Create and save a new access token.
36
37         Similar to OAuth 2, indication of granted scopes will be included as a
38         space separated list in ``oauth_authorized_realms``.
39
40         :param request: An oauthlib.common.Request object.
41         :returns: The token as an urlencoded string.
42         """
43         request.realms = self.request_validator.get_realms(
44             request.resource_owner_key, request)
45         token = {
46             'oauth_token': self.token_generator(),
47             'oauth_token_secret': self.token_generator(),
48             # Backport the authorized scopes indication used in OAuth2
49             'oauth_authorized_realms': ' '.join(request.realms)
50         }
51         token.update(credentials)
52         self.request_validator.save_access_token(token, request)
53         return urlencode(token.items())
54
55     def create_access_token_response(self, uri, http_method='GET', body=None,
56                                      headers=None, credentials=None):
57         """Create an access token response, with a new request token if valid.
58
59         :param uri: The full URI of the token request.
60         :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
61         :param body: The request body as a string.
62         :param headers: The request headers as a dict.
63         :param credentials: A list of extra credentials to include in the token.
64         :returns: A tuple of 3 elements.
65                   1. A dict of headers to set on the response.
66                   2. The response body as a string.
67                   3. The response status code as an integer.
68
69         An example of a valid request::
70
71             >>> from your_validator import your_validator
72             >>> from oauthlib.oauth1 import AccessTokenEndpoint
73             >>> endpoint = AccessTokenEndpoint(your_validator)
74             >>> h, b, s = endpoint.create_access_token_response(
75             ...     'https://your.provider/access_token?foo=bar',
76             ...     headers={
77             ...         'Authorization': 'OAuth oauth_token=234lsdkf....'
78             ...     },
79             ...     credentials={
80             ...         'my_specific': 'argument',
81             ...     })
82             >>> h
83             {'Content-Type': 'application/x-www-form-urlencoded'}
84             >>> b
85             'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument'
86             >>> s
87             200
88
89         An response to invalid request would have a different body and status::
90
91             >>> b
92             'error=invalid_request&description=missing+resource+owner+key'
93             >>> s
94             400
95
96         The same goes for an an unauthorized request:
97
98             >>> b
99             ''
100             >>> s
101             401
102         """
103         resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
104         try:
105             request = self._create_request(uri, http_method, body, headers)
106             valid, processed_request = self.validate_access_token_request(
107                 request)
108             if valid:
109                 token = self.create_access_token(request, credentials or {})
110                 self.request_validator.invalidate_request_token(
111                     request.client_key,
112                     request.resource_owner_key,
113                     request)
114                 return resp_headers, token, 200
115             else:
116                 return {}, None, 401
117         except errors.OAuth1Error as e:
118             return resp_headers, e.urlencoded, e.status_code
119
120     def validate_access_token_request(self, request):
121         """Validate an access token request.
122
123         :param request: An oauthlib.common.Request object.
124         :raises: OAuth1Error if the request is invalid.
125         :returns: A tuple of 2 elements.
126                   1. The validation result (True or False).
127                   2. The request object.
128         """
129         self._check_transport_security(request)
130         self._check_mandatory_parameters(request)
131
132         if not request.resource_owner_key:
133             raise errors.InvalidRequestError(
134                 description='Missing resource owner.')
135
136         if not self.request_validator.check_request_token(
137                 request.resource_owner_key):
138             raise errors.InvalidRequestError(
139                 description='Invalid resource owner key format.')
140
141         if not request.verifier:
142             raise errors.InvalidRequestError(
143                 description='Missing verifier.')
144
145         if not self.request_validator.check_verifier(request.verifier):
146             raise errors.InvalidRequestError(
147                 description='Invalid verifier format.')
148
149         if not self.request_validator.validate_timestamp_and_nonce(
150                 request.client_key, request.timestamp, request.nonce, request,
151                 request_token=request.resource_owner_key):
152             return False, request
153
154         # The server SHOULD return a 401 (Unauthorized) status code when
155         # receiving a request with invalid client credentials.
156         # Note: This is postponed in order to avoid timing attacks, instead
157         # a dummy client is assigned and used to maintain near constant
158         # time request verification.
159         #
160         # Note that early exit would enable client enumeration
161         valid_client = self.request_validator.validate_client_key(
162             request.client_key, request)
163         if not valid_client:
164             request.client_key = self.request_validator.dummy_client
165
166         # The server SHOULD return a 401 (Unauthorized) status code when
167         # receiving a request with invalid or expired token.
168         # Note: This is postponed in order to avoid timing attacks, instead
169         # a dummy token is assigned and used to maintain near constant
170         # time request verification.
171         #
172         # Note that early exit would enable resource owner enumeration
173         valid_resource_owner = self.request_validator.validate_request_token(
174             request.client_key, request.resource_owner_key, request)
175         if not valid_resource_owner:
176             request.resource_owner_key = self.request_validator.dummy_request_token
177
178         # The server MUST verify (Section 3.2) the validity of the request,
179         # ensure that the resource owner has authorized the provisioning of
180         # token credentials to the client, and ensure that the temporary
181         # credentials have not expired or been used before.  The server MUST
182         # also verify the verification code received from the client.
183         # .. _`Section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2
184         #
185         # Note that early exit would enable resource owner authorization
186         # verifier enumertion.
187         valid_verifier = self.request_validator.validate_verifier(
188             request.client_key,
189             request.resource_owner_key,
190             request.verifier,
191             request)
192
193         valid_signature = self._check_signature(request, is_token_request=True)
194
195         # We delay checking validity until the very end, using dummy values for
196         # calculations and fetching secrets/keys to ensure the flow of every
197         # request remains almost identical regardless of whether valid values
198         # have been supplied. This ensures near constant time execution and
199         # prevents malicious users from guessing sensitive information
200         v = all((valid_client, valid_resource_owner, valid_verifier,
201                  valid_signature))
202         if not v:
203             log.info("[Failure] request verification failed.")
204             log.info("Valid client:, %s", valid_client)
205             log.info("Valid token:, %s", valid_resource_owner)
206             log.info("Valid verifier:, %s", valid_verifier)
207             log.info("Valid signature:, %s", valid_signature)
208         return v, request

Benjamin Mako Hill || Want to submit a patch?