1 # -*- coding: utf-8 -*-
3 oauthlib.oauth2.rfc6749.parameters
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
8 .. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4
10 from __future__ import absolute_import, unicode_literals
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
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.
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`_]:
37 :param response_type: To indicate which OAuth 2 grant/flow is required,
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
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
51 :param kwargs: Extra arguments to embed in the grant/authorization URL.
53 An example of an authorization code grant authorization URL:
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
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
67 if not is_secure_transport(uri):
68 raise InsecureTransportError()
70 params = [(('response_type', response_type)),
71 (('client_id', client_id))]
74 params.append(('redirect_uri', redirect_uri))
76 params.append(('scope', list_to_scope(scope)))
78 params.append(('state', state))
82 params.append((unicode_type(k), kwargs[k]))
84 return add_params_to_uri(uri, params)
87 def prepare_token_request(grant_type, body='', **kwargs):
88 """Prepare the access token request.
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:
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.
104 An example of an authorization code token request body:
108 grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
109 &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
111 .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
113 params = [('grant_type', grant_type)]
115 if 'scope' in kwargs:
116 kwargs['scope'] = list_to_scope(kwargs['scope'])
120 params.append((unicode_type(k), kwargs[k]))
122 return add_params_to_qs(body, params)
125 def prepare_token_revocation_request(url, token, token_type_hint="access_token",
126 callback=None, body='', **kwargs):
127 """Prepare a token revocation request.
129 The client constructs the request by including the following parameters
130 using the "application/x-www-form-urlencoded" format in the HTTP request
133 token REQUIRED. The token that the client wants to get revoked.
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:
143 * access_token: An access token as defined in [RFC6749],
146 * refresh_token: A refresh token as defined in [RFC6749],
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`_.
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
158 if not is_secure_transport(url):
159 raise InsecureTransportError()
161 params = [('token', token)]
164 params.append(('token_type_hint', token_type_hint))
168 params.append((unicode_type(k), kwargs[k]))
170 headers = {'Content-Type': 'application/x-www-form-urlencoded'}
173 params.append(('callback', callback))
174 return add_params_to_uri(url, params), headers, body
176 return url, headers, add_params_to_qs(body, params)
179 def parse_authorization_code_response(uri, state=None):
180 """Parse authorization grant response URI into a dict.
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:
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.
200 REQUIRED if the "state" parameter was present in the client
201 authorization request. The exact value received from the
204 :param uri: The full redirect URL back to the client.
205 :param state: The state parameter from the authorization request.
207 For example, the authorization server redirects the user-agent by
208 sending the following HTTP response:
213 Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
217 if not is_secure_transport(uri):
218 raise InsecureTransportError()
220 query = urlparse.urlparse(uri).query
221 params = dict(urlparse.parse_qsl(query))
223 if not 'code' in params:
224 raise MissingCodeError("Missing code parameter in response.")
226 if state and params.get('state', None) != state:
227 raise MismatchingStateError()
232 def parse_implicit_response(uri, state=None, scope=None):
233 """Parse the implicit token response URI into a dict.
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:
241 REQUIRED. The access token issued by the authorization server.
244 REQUIRED. The type of the token issued as described in
245 Section 7.1. Value is case insensitive.
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.
255 OPTIONAL, if identical to the scope requested by the client,
256 otherwise REQUIRED. The scope of the access token as described
260 REQUIRED if the "state" parameter was present in the client
261 authorization request. The exact value received from the
264 Similar to the authorization code response, but with a full token provided
270 Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
271 &state=xyz&token_type=example&expires_in=3600
273 if not is_secure_transport(uri):
274 raise InsecureTransportError()
276 fragment = urlparse.urlparse(uri).fragment
277 params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
279 if 'scope' in params:
280 params['scope'] = scope_to_list(params['scope'])
282 if 'expires_in' in params:
283 params['expires_at'] = time.time() + int(params['expires_in'])
285 if state and params.get('state', None) != state:
286 raise ValueError("Mismatching or missing state in params.")
288 params = OAuth2Token(params, old_scope=scope)
289 validate_token_parameters(params)
293 def parse_token_response(body, scope=None):
294 """Parse the JSON token response body into a dict.
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:
301 REQUIRED. The access token issued by the authorization server.
303 REQUIRED. The type of the token issued as described in
304 `Section 7.1`_. Value is case insensitive.
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.
312 OPTIONAL. The refresh token which can be used to obtain new
313 access tokens using the same authorization grant as described
316 OPTIONAL, if identical to the scope requested by the client,
317 otherwise REQUIRED. The scope of the access token as described
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
328 :param body: The full json encoded response body.
329 :param scope: The scope requested during authorization.
336 Content-Type: application/json
337 Cache-Control: no-store
341 "access_token":"2YotnFZFEjr1zCsicMWpAA",
342 "token_type":"example",
344 "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
345 "example_parameter":"example_value"
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
354 params = json.loads(body)
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
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])
366 if 'scope' in params:
367 params['scope'] = scope_to_list(params['scope'])
369 if 'expires' in params:
370 params['expires_in'] = params.pop('expires')
372 if 'expires_in' in params:
373 params['expires_at'] = time.time() + int(params['expires_in'])
375 params = OAuth2Token(params, old_scope=scope)
376 validate_token_parameters(params)
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)
385 if not 'access_token' in params:
386 raise MissingTokenError(description="Missing access token parameter.")
388 if not 'token_type' in params:
389 if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
390 raise MissingTokenTypeError()
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,
400 scope_changed.send(message=message, old=params.old_scopes, new=params.scopes)
401 if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None):
404 w.old_scope = params.old_scopes
405 w.new_scope = params.scopes