gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / oauthlib / oauth2 / rfc6749 / parameters.py @ 564
History | View | Annotate | Download (15.5 KB)
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
|