1# -*- coding: utf-8 -*-
2"""
3oauthlib.oauth1.rfc5849.endpoints.access_token
4~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6This module is an implementation of the access token provider logic of
7OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
8creates and persists tokens as well as create the proper response to be
9returned to the client.
10"""
11import logging
12
13from oauthlib.common import urlencode
14
15from .. import errors
16from .base import BaseEndpoint
17
18log = logging.getLogger(__name__)
19
20
21class AccessTokenEndpoint(BaseEndpoint):
22
23 """An endpoint responsible for providing OAuth 1 access tokens.
24
25 Typical use is to instantiate with a request validator and invoke the
26 ``create_access_token_response`` from a view function. The tuple returned
27 has all information necessary (body, status, headers) to quickly form
28 and return a proper response. See :doc:`/oauth1/validator` for details on which
29 validator methods to implement for this endpoint.
30 """
31
32 def create_access_token(self, request, credentials):
33 """Create and save a new access token.
34
35 Similar to OAuth 2, indication of granted scopes will be included as a
36 space separated list in ``oauth_authorized_realms``.
37
38 :param request: OAuthlib request.
39 :type request: oauthlib.common.Request
40 :returns: The token as an urlencoded string.
41 """
42 request.realms = self.request_validator.get_realms(
43 request.resource_owner_key, request)
44 token = {
45 'oauth_token': self.token_generator(),
46 'oauth_token_secret': self.token_generator(),
47 # Backport the authorized scopes indication used in OAuth2
48 'oauth_authorized_realms': ' '.join(request.realms)
49 }
50 token.update(credentials)
51 self.request_validator.save_access_token(token, request)
52 return urlencode(token.items())
53
54 def create_access_token_response(self, uri, http_method='GET', body=None,
55 headers=None, credentials=None):
56 """Create an access token response, with a new request token if valid.
57
58 :param uri: The full URI of the token request.
59 :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
60 :param body: The request body as a string.
61 :param headers: The request headers as a dict.
62 :param credentials: A list of extra credentials to include in the token.
63 :returns: A tuple of 3 elements.
64 1. A dict of headers to set on the response.
65 2. The response body as a string.
66 3. The response status code as an integer.
67
68 An example of a valid request::
69
70 >>> from your_validator import your_validator
71 >>> from oauthlib.oauth1 import AccessTokenEndpoint
72 >>> endpoint = AccessTokenEndpoint(your_validator)
73 >>> h, b, s = endpoint.create_access_token_response(
74 ... 'https://your.provider/access_token?foo=bar',
75 ... headers={
76 ... 'Authorization': 'OAuth oauth_token=234lsdkf....'
77 ... },
78 ... credentials={
79 ... 'my_specific': 'argument',
80 ... })
81 >>> h
82 {'Content-Type': 'application/x-www-form-urlencoded'}
83 >>> b
84 'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument'
85 >>> s
86 200
87
88 An response to invalid request would have a different body and status::
89
90 >>> b
91 'error=invalid_request&description=missing+resource+owner+key'
92 >>> s
93 400
94
95 The same goes for an an unauthorized request:
96
97 >>> b
98 ''
99 >>> s
100 401
101 """
102 resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
103 try:
104 request = self._create_request(uri, http_method, body, headers)
105 valid, processed_request = self.validate_access_token_request(
106 request)
107 if valid:
108 token = self.create_access_token(request, credentials or {})
109 self.request_validator.invalidate_request_token(
110 request.client_key,
111 request.resource_owner_key,
112 request)
113 return resp_headers, token, 200
114 else:
115 return {}, None, 401
116 except errors.OAuth1Error as e:
117 return resp_headers, e.urlencoded, e.status_code
118
119 def validate_access_token_request(self, request):
120 """Validate an access token request.
121
122 :param request: OAuthlib request.
123 :type request: oauthlib.common.Request
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`: https://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 # log the results to the validator_log
196 # this lets us handle internal reporting and analysis
197 request.validator_log['client'] = valid_client
198 request.validator_log['resource_owner'] = valid_resource_owner
199 request.validator_log['verifier'] = valid_verifier
200 request.validator_log['signature'] = valid_signature
201
202 # We delay checking validity until the very end, using dummy values for
203 # calculations and fetching secrets/keys to ensure the flow of every
204 # request remains almost identical regardless of whether valid values
205 # have been supplied. This ensures near constant time execution and
206 # prevents malicious users from guessing sensitive information
207 v = all((valid_client, valid_resource_owner, valid_verifier,
208 valid_signature))
209 if not v:
210 log.info("[Failure] request verification failed.")
211 log.info("Valid client:, %s", valid_client)
212 log.info("Valid token:, %s", valid_resource_owner)
213 log.info("Valid verifier:, %s", valid_verifier)
214 log.info("Valid signature:, %s", valid_signature)
215 return v, request