import oauthlib (a dependency of the project)
[yelp-api-cdsw] / oauthlib / oauth1 / rfc5849 / endpoints / request_token.py
1 # -*- coding: utf-8 -*-
2 """
3 oauthlib.oauth1.rfc5849.endpoints.request_token
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 This module is an implementation of the request token provider logic of
7 OAuth 1.0 RFC 5849. It validates the correctness of request 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 RequestTokenEndpoint(BaseEndpoint):
24
25     """An endpoint responsible for providing OAuth 1 request tokens.
26
27     Typical use is to instantiate with a request validator and invoke the
28     ``create_request_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_request_token(self, request, credentials):
35         """Create and save a new request token.
36
37         :param request: An oauthlib.common.Request object.
38         :param credentials: A dict of extra token credentials.
39         :returns: The token as an urlencoded string.
40         """
41         token = {
42             'oauth_token': self.token_generator(),
43             'oauth_token_secret': self.token_generator(),
44             'oauth_callback_confirmed': 'true'
45         }
46         token.update(credentials)
47         self.request_validator.save_request_token(token, request)
48         return urlencode(token.items())
49
50     def create_request_token_response(self, uri, http_method='GET', body=None,
51                                       headers=None, credentials=None):
52         """Create a request token response, with a new request token if valid.
53
54         :param uri: The full URI of the token request.
55         :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
56         :param body: The request body as a string.
57         :param headers: The request headers as a dict.
58         :param credentials: A list of extra credentials to include in the token.
59         :returns: A tuple of 3 elements.
60                   1. A dict of headers to set on the response.
61                   2. The response body as a string.
62                   3. The response status code as an integer.
63
64         An example of a valid request::
65
66             >>> from your_validator import your_validator
67             >>> from oauthlib.oauth1 import RequestTokenEndpoint
68             >>> endpoint = RequestTokenEndpoint(your_validator)
69             >>> h, b, s = endpoint.create_request_token_response(
70             ...     'https://your.provider/request_token?foo=bar',
71             ...     headers={
72             ...         'Authorization': 'OAuth realm=movies user, oauth_....'
73             ...     },
74             ...     credentials={
75             ...         'my_specific': 'argument',
76             ...     })
77             >>> h
78             {'Content-Type': 'application/x-www-form-urlencoded'}
79             >>> b
80             'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_callback_confirmed=true&my_specific=argument'
81             >>> s
82             200
83
84         An response to invalid request would have a different body and status::
85
86             >>> b
87             'error=invalid_request&description=missing+callback+uri'
88             >>> s
89             400
90
91         The same goes for an an unauthorized request:
92
93             >>> b
94             ''
95             >>> s
96             401
97         """
98         resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
99         try:
100             request = self._create_request(uri, http_method, body, headers)
101             valid, processed_request = self.validate_request_token_request(
102                 request)
103             if valid:
104                 token = self.create_request_token(request, credentials or {})
105                 return resp_headers, token, 200
106             else:
107                 return {}, None, 401
108         except errors.OAuth1Error as e:
109             return resp_headers, e.urlencoded, e.status_code
110
111     def validate_request_token_request(self, request):
112         """Validate a request token request.
113
114         :param request: An oauthlib.common.Request object.
115         :raises: OAuth1Error if the request is invalid.
116         :returns: A tuple of 2 elements.
117                   1. The validation result (True or False).
118                   2. The request object.
119         """
120         self._check_transport_security(request)
121         self._check_mandatory_parameters(request)
122
123         if request.realm:
124             request.realms = request.realm.split(' ')
125         else:
126             request.realms = self.request_validator.get_default_realms(
127                 request.client_key, request)
128         if not self.request_validator.check_realms(request.realms):
129             raise errors.InvalidRequestError(
130                 description='Invalid realm %s. Allowed are %r.' % (
131                     request.realms, self.request_validator.realms))
132
133         if not request.redirect_uri:
134             raise errors.InvalidRequestError(
135                 description='Missing callback URI.')
136
137         if not self.request_validator.validate_timestamp_and_nonce(
138                 request.client_key, request.timestamp, request.nonce, request,
139                 request_token=request.resource_owner_key):
140             return False, request
141
142         # The server SHOULD return a 401 (Unauthorized) status code when
143         # receiving a request with invalid client credentials.
144         # Note: This is postponed in order to avoid timing attacks, instead
145         # a dummy client is assigned and used to maintain near constant
146         # time request verification.
147         #
148         # Note that early exit would enable client enumeration
149         valid_client = self.request_validator.validate_client_key(
150             request.client_key, request)
151         if not valid_client:
152             request.client_key = self.request_validator.dummy_client
153
154         # Note that `realm`_ is only used in authorization headers and how
155         # it should be interepreted is not included in the OAuth spec.
156         # However they could be seen as a scope or realm to which the
157         # client has access and as such every client should be checked
158         # to ensure it is authorized access to that scope or realm.
159         # .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2
160         #
161         # Note that early exit would enable client realm access enumeration.
162         #
163         # The require_realm indicates this is the first step in the OAuth
164         # workflow where a client requests access to a specific realm.
165         # This first step (obtaining request token) need not require a realm
166         # and can then be identified by checking the require_resource_owner
167         # flag and abscence of realm.
168         #
169         # Clients obtaining an access token will not supply a realm and it will
170         # not be checked. Instead the previously requested realm should be
171         # transferred from the request token to the access token.
172         #
173         # Access to protected resources will always validate the realm but note
174         # that the realm is now tied to the access token and not provided by
175         # the client.
176         valid_realm = self.request_validator.validate_requested_realms(
177             request.client_key, request.realms, request)
178
179         # Callback is normally never required, except for requests for
180         # a Temporary Credential as described in `Section 2.1`_
181         # .._`Section 2.1`: http://tools.ietf.org/html/rfc5849#section-2.1
182         valid_redirect = self.request_validator.validate_redirect_uri(
183             request.client_key, request.redirect_uri, request)
184         if not request.redirect_uri:
185             raise NotImplementedError('Redirect URI must either be provided '
186                                       'or set to a default during validation.')
187
188         valid_signature = self._check_signature(request)
189
190         # We delay checking validity until the very end, using dummy values for
191         # calculations and fetching secrets/keys to ensure the flow of every
192         # request remains almost identical regardless of whether valid values
193         # have been supplied. This ensures near constant time execution and
194         # prevents malicious users from guessing sensitive information
195         v = all((valid_client, valid_realm, valid_redirect, valid_signature))
196         if not v:
197             log.info("[Failure] request verification failed.")
198             log.info("Valid client: %s.", valid_client)
199             log.info("Valid realm: %s.", valid_realm)
200             log.info("Valid callback: %s.", valid_redirect)
201             log.info("Valid signature: %s.", valid_signature)
202         return v, request

Benjamin Mako Hill || Want to submit a patch?