]> projects.mako.cc - twitter-api-cdsw/blob - oauthlib/oauth2/rfc6749/parameters.py
reverted the encoding fix that tommy made in lieu of a different crazy hack
[twitter-api-cdsw] / oauthlib / oauth2 / rfc6749 / parameters.py
1 # -*- coding: utf-8 -*-
2 """
3 oauthlib.oauth2.rfc6749.parameters
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
7
8 .. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4
9 """
10 from __future__ import absolute_import, unicode_literals
11
12 import json
13 import os
14 import time
15 try:
16     import urlparse
17 except ImportError:
18     import urllib.parse as urlparse
19 from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
20 from oauthlib.signals import scope_changed
21 from .errors import raise_from_error, MissingTokenError, MissingTokenTypeError
22 from .errors import MismatchingStateError, MissingCodeError
23 from .errors import InsecureTransportError
24 from .tokens import OAuth2Token
25 from .utils import list_to_scope, scope_to_list, is_secure_transport
26
27
28 def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
29                       scope=None, state=None, **kwargs):
30     """Prepare the authorization grant request URI.
31
32     The client constructs the request URI by adding the following
33     parameters to the query component of the authorization endpoint URI
34     using the ``application/x-www-form-urlencoded`` format as defined by
35     [`W3C.REC-html401-19991224`_]:
36
37     :param response_type: To indicate which OAuth 2 grant/flow is required,
38                           "code" and "token".
39     :param client_id: The client identifier as described in `Section 2.2`_.
40     :param redirect_uri: The client provided URI to redirect back to after
41                          authorization as described in `Section 3.1.2`_.
42     :param scope: The scope of the access request as described by
43                   `Section 3.3`_.
44
45     :param state: An opaque value used by the client to maintain
46                   state between the request and callback.  The authorization
47                   server includes this value when redirecting the user-agent
48                   back to the client.  The parameter SHOULD be used for
49                   preventing cross-site request forgery as described in
50                   `Section 10.12`_.
51     :param kwargs: Extra arguments to embed in the grant/authorization URL.
52
53     An example of an authorization code grant authorization URL:
54
55     .. code-block:: http
56
57         GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
58             &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
59         Host: server.example.com
60
61     .. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
62     .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
63     .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
64     .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
65     .. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
66     """
67     if not is_secure_transport(uri):
68         raise InsecureTransportError()
69
70     params = [(('response_type', response_type)),
71               (('client_id', client_id))]
72
73     if redirect_uri:
74         params.append(('redirect_uri', redirect_uri))
75     if scope:
76         params.append(('scope', list_to_scope(scope)))
77     if state:
78         params.append(('state', state))
79
80     for k in kwargs:
81         if kwargs[k]:
82             params.append((unicode_type(k), kwargs[k]))
83
84     return add_params_to_uri(uri, params)
85
86
87 def prepare_token_request(grant_type, body='', **kwargs):
88     """Prepare the access token request.
89
90     The client makes a request to the token endpoint by adding the
91     following parameters using the ``application/x-www-form-urlencoded``
92     format in the HTTP request entity-body:
93
94     :param grant_type: To indicate grant type being used, i.e. "password",
95             "authorization_code" or "client_credentials".
96     :param body: Existing request body to embed parameters in.
97     :param code: If using authorization code grant, pass the previously
98                  obtained authorization code as the ``code`` argument.
99     :param redirect_uri: If the "redirect_uri" parameter was included in the
100                          authorization request as described in
101                          `Section 4.1.1`_, and their values MUST be identical.
102     :param kwargs: Extra arguments to embed in the request body.
103
104     An example of an authorization code token request body:
105
106     .. code-block:: http
107
108         grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
109         &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
110
111     .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
112     """
113     params = [('grant_type', grant_type)]
114
115     if 'scope' in kwargs:
116         kwargs['scope'] = list_to_scope(kwargs['scope'])
117
118     for k in kwargs:
119         if kwargs[k]:
120             params.append((unicode_type(k), kwargs[k]))
121
122     return add_params_to_qs(body, params)
123
124
125 def prepare_token_revocation_request(url, token, token_type_hint="access_token",
126         callback=None, body='', **kwargs):
127     """Prepare a token revocation request.
128
129     The client constructs the request by including the following parameters
130     using the "application/x-www-form-urlencoded" format in the HTTP request
131     entity-body:
132
133     token   REQUIRED.  The token that the client wants to get revoked.
134
135     token_type_hint  OPTIONAL.  A hint about the type of the token submitted
136     for revocation.  Clients MAY pass this parameter in order to help the
137     authorization server to optimize the token lookup.  If the server is unable
138     to locate the token using the given hint, it MUST extend its search across
139     all of its supported token types.  An authorization server MAY ignore this
140     parameter, particularly if it is able to detect the token type
141     automatically.  This specification defines two such values:
142
143         * access_token: An access token as defined in [RFC6749],
144              `Section 1.4`_
145
146         * refresh_token: A refresh token as defined in [RFC6749],
147              `Section 1.5`_
148
149         Specific implementations, profiles, and extensions of this
150         specification MAY define other values for this parameter using the
151         registry defined in `Section 4.1.2`_.
152
153     .. _`Section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
154     .. _`Section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
155     .. _`Section 4.1.2`: http://tools.ietf.org/html/rfc7009#section-4.1.2
156
157     """
158     if not is_secure_transport(url):
159         raise InsecureTransportError()
160
161     params = [('token', token)]
162
163     if token_type_hint:
164         params.append(('token_type_hint', token_type_hint))
165
166     for k in kwargs:
167         if kwargs[k]:
168             params.append((unicode_type(k), kwargs[k]))
169
170     headers = {'Content-Type': 'application/x-www-form-urlencoded'}
171
172     if callback:
173         params.append(('callback', callback))
174         return add_params_to_uri(url, params), headers, body
175     else:
176         return url, headers, add_params_to_qs(body, params)
177
178
179 def parse_authorization_code_response(uri, state=None):
180     """Parse authorization grant response URI into a dict.
181
182     If the resource owner grants the access request, the authorization
183     server issues an authorization code and delivers it to the client by
184     adding the following parameters to the query component of the
185     redirection URI using the ``application/x-www-form-urlencoded`` format:
186
187     **code**
188             REQUIRED.  The authorization code generated by the
189             authorization server.  The authorization code MUST expire
190             shortly after it is issued to mitigate the risk of leaks.  A
191             maximum authorization code lifetime of 10 minutes is
192             RECOMMENDED.  The client MUST NOT use the authorization code
193             more than once.  If an authorization code is used more than
194             once, the authorization server MUST deny the request and SHOULD
195             revoke (when possible) all tokens previously issued based on
196             that authorization code.  The authorization code is bound to
197             the client identifier and redirection URI.
198
199     **state**
200             REQUIRED if the "state" parameter was present in the client
201             authorization request.  The exact value received from the
202             client.
203
204     :param uri: The full redirect URL back to the client.
205     :param state: The state parameter from the authorization request.
206
207     For example, the authorization server redirects the user-agent by
208     sending the following HTTP response:
209
210     .. code-block:: http
211
212         HTTP/1.1 302 Found
213         Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
214                 &state=xyz
215
216     """
217     if not is_secure_transport(uri):
218         raise InsecureTransportError()
219
220     query = urlparse.urlparse(uri).query
221     params = dict(urlparse.parse_qsl(query))
222
223     if not 'code' in params:
224         raise MissingCodeError("Missing code parameter in response.")
225
226     if state and params.get('state', None) != state:
227         raise MismatchingStateError()
228
229     return params
230
231
232 def parse_implicit_response(uri, state=None, scope=None):
233     """Parse the implicit token response URI into a dict.
234
235     If the resource owner grants the access request, the authorization
236     server issues an access token and delivers it to the client by adding
237     the following parameters to the fragment component of the redirection
238     URI using the ``application/x-www-form-urlencoded`` format:
239
240     **access_token**
241             REQUIRED.  The access token issued by the authorization server.
242
243     **token_type**
244             REQUIRED.  The type of the token issued as described in
245             Section 7.1.  Value is case insensitive.
246
247     **expires_in**
248             RECOMMENDED.  The lifetime in seconds of the access token.  For
249             example, the value "3600" denotes that the access token will
250             expire in one hour from the time the response was generated.
251             If omitted, the authorization server SHOULD provide the
252             expiration time via other means or document the default value.
253
254     **scope**
255             OPTIONAL, if identical to the scope requested by the client,
256             otherwise REQUIRED.  The scope of the access token as described
257             by Section 3.3.
258
259     **state**
260             REQUIRED if the "state" parameter was present in the client
261             authorization request.  The exact value received from the
262             client.
263
264     Similar to the authorization code response, but with a full token provided
265     in the URL fragment:
266
267     .. code-block:: http
268
269         HTTP/1.1 302 Found
270         Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
271                 &state=xyz&token_type=example&expires_in=3600
272     """
273     if not is_secure_transport(uri):
274         raise InsecureTransportError()
275
276     fragment = urlparse.urlparse(uri).fragment
277     params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
278
279     if 'scope' in params:
280         params['scope'] = scope_to_list(params['scope'])
281
282     if 'expires_in' in params:
283         params['expires_at'] = time.time() + int(params['expires_in'])
284
285     if state and params.get('state', None) != state:
286         raise ValueError("Mismatching or missing state in params.")
287
288     params = OAuth2Token(params, old_scope=scope)
289     validate_token_parameters(params)
290     return params
291
292
293 def parse_token_response(body, scope=None):
294     """Parse the JSON token response body into a dict.
295
296     The authorization server issues an access token and optional refresh
297     token, and constructs the response by adding the following parameters
298     to the entity body of the HTTP response with a 200 (OK) status code:
299
300     access_token
301             REQUIRED.  The access token issued by the authorization server.
302     token_type
303             REQUIRED.  The type of the token issued as described in
304             `Section 7.1`_.  Value is case insensitive.
305     expires_in
306             RECOMMENDED.  The lifetime in seconds of the access token.  For
307             example, the value "3600" denotes that the access token will
308             expire in one hour from the time the response was generated.
309             If omitted, the authorization server SHOULD provide the
310             expiration time via other means or document the default value.
311     refresh_token
312             OPTIONAL.  The refresh token which can be used to obtain new
313             access tokens using the same authorization grant as described
314             in `Section 6`_.
315     scope
316             OPTIONAL, if identical to the scope requested by the client,
317             otherwise REQUIRED.  The scope of the access token as described
318             by `Section 3.3`_.
319
320     The parameters are included in the entity body of the HTTP response
321     using the "application/json" media type as defined by [`RFC4627`_].  The
322     parameters are serialized into a JSON structure by adding each
323     parameter at the highest structure level.  Parameter names and string
324     values are included as JSON strings.  Numerical values are included
325     as JSON numbers.  The order of parameters does not matter and can
326     vary.
327
328     :param body: The full json encoded response body.
329     :param scope: The scope requested during authorization.
330
331     For example:
332
333     .. code-block:: http
334
335         HTTP/1.1 200 OK
336         Content-Type: application/json
337         Cache-Control: no-store
338         Pragma: no-cache
339
340         {
341             "access_token":"2YotnFZFEjr1zCsicMWpAA",
342             "token_type":"example",
343             "expires_in":3600,
344             "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
345             "example_parameter":"example_value"
346         }
347
348     .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
349     .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
350     .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
351     .. _`RFC4627`: http://tools.ietf.org/html/rfc4627
352     """
353     try:
354         params = json.loads(body)
355     except ValueError:
356
357         # Fall back to URL-encoded string, to support old implementations,
358         # including (at time of writing) Facebook. See:
359         #   https://github.com/idan/oauthlib/issues/267
360
361         params = dict(urlparse.parse_qsl(body))
362         for key in ('expires_in', 'expires'):
363             if key in params:  # cast a couple things to int
364                 params[key] = int(params[key])
365
366     if 'scope' in params:
367         params['scope'] = scope_to_list(params['scope'])
368
369     if 'expires' in params:
370         params['expires_in'] = params.pop('expires')
371
372     if 'expires_in' in params:
373         params['expires_at'] = time.time() + int(params['expires_in'])
374
375     params = OAuth2Token(params, old_scope=scope)
376     validate_token_parameters(params)
377     return params
378
379
380 def validate_token_parameters(params):
381     """Ensures token precence, token type, expiration and scope in params."""
382     if 'error' in params:
383         raise_from_error(params.get('error'), params)
384
385     if not 'access_token' in params:
386         raise MissingTokenError(description="Missing access token parameter.")
387
388     if not 'token_type' in params:
389         if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
390             raise MissingTokenTypeError()
391
392     # If the issued access token scope is different from the one requested by
393     # the client, the authorization server MUST include the "scope" response
394     # parameter to inform the client of the actual scope granted.
395     # http://tools.ietf.org/html/rfc6749#section-3.3
396     if params.scope_changed:
397         message = 'Scope has changed from "{old}" to "{new}".'.format(
398             old=params.old_scope, new=params.scope,
399         )
400         scope_changed.send(message=message, old=params.old_scopes, new=params.scopes)
401         if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None):
402             w = Warning(message)
403             w.token = params
404             w.old_scope = params.old_scopes
405             w.new_scope = params.scopes
406             raise w

Benjamin Mako Hill || Want to submit a patch?