1 # -*- coding: utf-8 -*-
3 oauthlib.oauth1.rfc5849.endpoints.request_token
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
11 from __future__ import absolute_import, unicode_literals
15 from oauthlib.common import urlencode
17 from .base import BaseEndpoint
20 log = logging.getLogger(__name__)
23 class RequestTokenEndpoint(BaseEndpoint):
25 """An endpoint responsible for providing OAuth 1 request tokens.
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.
34 def create_request_token(self, request, credentials):
35 """Create and save a new request token.
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.
42 'oauth_token': self.token_generator(),
43 'oauth_token_secret': self.token_generator(),
44 'oauth_callback_confirmed': 'true'
46 token.update(credentials)
47 self.request_validator.save_request_token(token, request)
48 return urlencode(token.items())
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.
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.
64 An example of a valid request::
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',
72 ... 'Authorization': 'OAuth realm=movies user, oauth_....'
75 ... 'my_specific': 'argument',
78 {'Content-Type': 'application/x-www-form-urlencoded'}
80 'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_callback_confirmed=true&my_specific=argument'
84 An response to invalid request would have a different body and status::
87 'error=invalid_request&description=missing+callback+uri'
91 The same goes for an an unauthorized request:
98 resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
100 request = self._create_request(uri, http_method, body, headers)
101 valid, processed_request = self.validate_request_token_request(
104 token = self.create_request_token(request, credentials or {})
105 return resp_headers, token, 200
108 except errors.OAuth1Error as e:
109 return resp_headers, e.urlencoded, e.status_code
111 def validate_request_token_request(self, request):
112 """Validate a request token request.
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.
120 self._check_transport_security(request)
121 self._check_mandatory_parameters(request)
124 request.realms = request.realm.split(' ')
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))
133 if not request.redirect_uri:
134 raise errors.InvalidRequestError(
135 description='Missing callback URI.')
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
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.
148 # Note that early exit would enable client enumeration
149 valid_client = self.request_validator.validate_client_key(
150 request.client_key, request)
152 request.client_key = self.request_validator.dummy_client
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
161 # Note that early exit would enable client realm access enumeration.
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.
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.
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
176 valid_realm = self.request_validator.validate_requested_realms(
177 request.client_key, request.realms, request)
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.')
188 valid_signature = self._check_signature(request)
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))
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)