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 / clients / base.py @ 564
History | View | Annotate | Download (19.9 KB)
1 |
# -*- coding: utf-8 -*-
|
---|---|
2 |
"""
|
3 |
oauthlib.oauth2.rfc6749
|
4 |
~~~~~~~~~~~~~~~~~~~~~~~
|
5 |
|
6 |
This module is an implementation of various logic needed
|
7 |
for consuming OAuth 2.0 RFC6749.
|
8 |
"""
|
9 |
from __future__ import absolute_import, unicode_literals |
10 |
|
11 |
import time |
12 |
|
13 |
from oauthlib.common import generate_token |
14 |
from oauthlib.oauth2.rfc6749 import tokens |
15 |
from oauthlib.oauth2.rfc6749.parameters import parse_token_response |
16 |
from oauthlib.oauth2.rfc6749.parameters import prepare_token_request |
17 |
from oauthlib.oauth2.rfc6749.parameters import prepare_token_revocation_request |
18 |
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError |
19 |
from oauthlib.oauth2.rfc6749.errors import InsecureTransportError |
20 |
from oauthlib.oauth2.rfc6749.utils import is_secure_transport |
21 |
|
22 |
|
23 |
AUTH_HEADER = 'auth_header'
|
24 |
URI_QUERY = 'query'
|
25 |
BODY = 'body'
|
26 |
|
27 |
FORM_ENC_HEADERS = { |
28 |
'Content-Type': 'application/x-www-form-urlencoded' |
29 |
} |
30 |
|
31 |
class Client(object): |
32 |
|
33 |
"""Base OAuth2 client responsible for access token management.
|
34 |
|
35 |
This class also acts as a generic interface providing methods common to all
|
36 |
client types such as ``prepare_authorization_request`` and
|
37 |
``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
|
38 |
the recommended way of interacting with clients (as opposed to the abstract
|
39 |
prepare uri/body/etc methods). They are recommended over the older set
|
40 |
because they are easier to use (more consistent) and add a few additional
|
41 |
security checks, such as HTTPS and state checking.
|
42 |
|
43 |
Some of these methods require further implementation only provided by the
|
44 |
specific purpose clients such as
|
45 |
:py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
|
46 |
seek to use the client class matching the OAuth workflow you need. For
|
47 |
Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
|
48 |
|
49 |
"""
|
50 |
|
51 |
def __init__(self, client_id, |
52 |
default_token_placement=AUTH_HEADER, |
53 |
token_type='Bearer',
|
54 |
access_token=None,
|
55 |
refresh_token=None,
|
56 |
mac_key=None,
|
57 |
mac_algorithm=None,
|
58 |
token=None,
|
59 |
scope=None,
|
60 |
state=None,
|
61 |
redirect_url=None,
|
62 |
state_generator=generate_token, |
63 |
**kwargs): |
64 |
"""Initialize a client with commonly used attributes.
|
65 |
|
66 |
:param client_id: Client identifier given by the OAuth provider upon
|
67 |
registration.
|
68 |
|
69 |
:param default_token_placement: Tokens can be supplied in the Authorization
|
70 |
header (default), the URL query component (``query``) or the request
|
71 |
body (``body``).
|
72 |
|
73 |
:param token_type: OAuth 2 token type. Defaults to Bearer. Change this
|
74 |
if you specify the ``access_token`` parameter and know it is of a
|
75 |
different token type, such as a MAC, JWT or SAML token. Can
|
76 |
also be supplied as ``token_type`` inside the ``token`` dict parameter.
|
77 |
|
78 |
:param access_token: An access token (string) used to authenticate
|
79 |
requests to protected resources. Can also be supplied inside the
|
80 |
``token`` dict parameter.
|
81 |
|
82 |
:param refresh_token: A refresh token (string) used to refresh expired
|
83 |
tokens. Can also be supplide inside the ``token`` dict parameter.
|
84 |
|
85 |
:param mac_key: Encryption key used with MAC tokens.
|
86 |
|
87 |
:param mac_algorithm: Hashing algorithm for MAC tokens.
|
88 |
|
89 |
:param token: A dict of token attributes such as ``access_token``,
|
90 |
``token_type`` and ``expires_at``.
|
91 |
|
92 |
:param scope: A list of default scopes to request authorization for.
|
93 |
|
94 |
:param state: A CSRF protection string used during authorization.
|
95 |
|
96 |
:param redirect_url: The redirection endpoint on the client side to which
|
97 |
the user returns after authorization.
|
98 |
|
99 |
:param state_generator: A no argument state generation callable. Defaults
|
100 |
to :py:meth:`oauthlib.common.generate_token`.
|
101 |
"""
|
102 |
|
103 |
self.client_id = client_id
|
104 |
self.default_token_placement = default_token_placement
|
105 |
self.token_type = token_type
|
106 |
self.access_token = access_token
|
107 |
self.refresh_token = refresh_token
|
108 |
self.mac_key = mac_key
|
109 |
self.mac_algorithm = mac_algorithm
|
110 |
self.token = token or {} |
111 |
self.scope = scope
|
112 |
self.state_generator = state_generator
|
113 |
self.state = state
|
114 |
self.redirect_url = redirect_url
|
115 |
self._expires_at = None |
116 |
self._populate_attributes(self.token) |
117 |
|
118 |
@property
|
119 |
def token_types(self): |
120 |
"""Supported token types and their respective methods
|
121 |
|
122 |
Additional tokens can be supported by extending this dictionary.
|
123 |
|
124 |
The Bearer token spec is stable and safe to use.
|
125 |
|
126 |
The MAC token spec is not yet stable and support for MAC tokens
|
127 |
is experimental and currently matching version 00 of the spec.
|
128 |
"""
|
129 |
return {
|
130 |
'Bearer': self._add_bearer_token, |
131 |
'MAC': self._add_mac_token |
132 |
} |
133 |
|
134 |
def prepare_request_uri(self, *args, **kwargs): |
135 |
"""Abstract method used to create request URIs."""
|
136 |
raise NotImplementedError("Must be implemented by inheriting classes.") |
137 |
|
138 |
def prepare_request_body(self, *args, **kwargs): |
139 |
"""Abstract method used to create request bodies."""
|
140 |
raise NotImplementedError("Must be implemented by inheriting classes.") |
141 |
|
142 |
def parse_request_uri_response(self, *args, **kwargs): |
143 |
"""Abstract method used to parse redirection responses."""
|
144 |
|
145 |
def add_token(self, uri, http_method='GET', body=None, headers=None, |
146 |
token_placement=None, **kwargs):
|
147 |
"""Add token to the request uri, body or authorization header.
|
148 |
|
149 |
The access token type provides the client with the information
|
150 |
required to successfully utilize the access token to make a protected
|
151 |
resource request (along with type-specific attributes). The client
|
152 |
MUST NOT use an access token if it does not understand the token
|
153 |
type.
|
154 |
|
155 |
For example, the "bearer" token type defined in
|
156 |
[`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
|
157 |
token string in the request:
|
158 |
|
159 |
.. code-block:: http
|
160 |
|
161 |
GET /resource/1 HTTP/1.1
|
162 |
Host: example.com
|
163 |
Authorization: Bearer mF_9.B5f-4.1JqM
|
164 |
|
165 |
while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
|
166 |
utilized by issuing a MAC key together with the access token which is
|
167 |
used to sign certain components of the HTTP requests:
|
168 |
|
169 |
.. code-block:: http
|
170 |
|
171 |
GET /resource/1 HTTP/1.1
|
172 |
Host: example.com
|
173 |
Authorization: MAC id="h480djs93hd8",
|
174 |
nonce="274312:dj83hs9s",
|
175 |
mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
|
176 |
|
177 |
.. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2
|
178 |
.. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2
|
179 |
"""
|
180 |
if not is_secure_transport(uri): |
181 |
raise InsecureTransportError()
|
182 |
|
183 |
token_placement = token_placement or self.default_token_placement |
184 |
|
185 |
case_insensitive_token_types = dict(
|
186 |
(k.lower(), v) for k, v in self.token_types.items()) |
187 |
if not self.token_type.lower() in case_insensitive_token_types: |
188 |
raise ValueError("Unsupported token type: %s" % self.token_type) |
189 |
|
190 |
if not self.access_token: |
191 |
raise ValueError("Missing access token.") |
192 |
|
193 |
if self._expires_at and self._expires_at < time.time(): |
194 |
raise TokenExpiredError()
|
195 |
|
196 |
return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body, |
197 |
headers, token_placement, **kwargs) |
198 |
|
199 |
def prepare_authorization_request(self, authorization_url, state=None, |
200 |
redirect_url=None, scope=None, **kwargs): |
201 |
"""Prepare the authorization request.
|
202 |
|
203 |
This is the first step in many OAuth flows in which the user is
|
204 |
redirected to a certain authorization URL. This method adds
|
205 |
required parameters to the authorization URL.
|
206 |
|
207 |
:param authorization_url: Provider authorization endpoint URL.
|
208 |
|
209 |
:param state: CSRF protection string. Will be automatically created if
|
210 |
not provided. The generated state is available via the ``state``
|
211 |
attribute. Clients should verify that the state is unchanged and
|
212 |
present in the authorization response. This verification is done
|
213 |
automatically if using the ``authorization_response`` parameter
|
214 |
with ``prepare_token_request``.
|
215 |
|
216 |
:param redirect_url: Redirect URL to which the user will be returned
|
217 |
after authorization. Must be provided unless previously setup with
|
218 |
the provider. If provided then it must also be provided in the
|
219 |
token request.
|
220 |
|
221 |
:param kwargs: Additional parameters to included in the request.
|
222 |
|
223 |
:returns: The prepared request tuple with (url, headers, body).
|
224 |
"""
|
225 |
if not is_secure_transport(authorization_url): |
226 |
raise InsecureTransportError()
|
227 |
|
228 |
self.state = state or self.state_generator() |
229 |
self.redirect_url = redirect_url or self.redirect_url |
230 |
self.scope = scope or self.scope |
231 |
auth_url = self.prepare_request_uri(
|
232 |
authorization_url, redirect_uri=self.redirect_url,
|
233 |
scope=self.scope, state=self.state, **kwargs) |
234 |
return auth_url, FORM_ENC_HEADERS, '' |
235 |
|
236 |
def prepare_token_request(self, token_url, authorization_response=None, |
237 |
redirect_url=None, state=None, body='', **kwargs): |
238 |
"""Prepare a token creation request.
|
239 |
|
240 |
Note that these requests usually require client authentication, either
|
241 |
by including client_id or a set of provider specific authentication
|
242 |
credentials.
|
243 |
|
244 |
:param token_url: Provider token creation endpoint URL.
|
245 |
|
246 |
:param authorization_response: The full redirection URL string, i.e.
|
247 |
the location to which the user was redirected after successfull
|
248 |
authorization. Used to mine credentials needed to obtain a token
|
249 |
in this step, such as authorization code.
|
250 |
|
251 |
:param redirect_url: The redirect_url supplied with the authorization
|
252 |
request (if there was one).
|
253 |
|
254 |
:param body: Request body (URL encoded string).
|
255 |
|
256 |
:param kwargs: Additional parameters to included in the request.
|
257 |
|
258 |
:returns: The prepared request tuple with (url, headers, body).
|
259 |
"""
|
260 |
if not is_secure_transport(token_url): |
261 |
raise InsecureTransportError()
|
262 |
|
263 |
state = state or self.state |
264 |
if authorization_response:
|
265 |
self.parse_request_uri_response(
|
266 |
authorization_response, state=state) |
267 |
self.redirect_url = redirect_url or self.redirect_url |
268 |
body = self.prepare_request_body(body=body,
|
269 |
redirect_uri=self.redirect_url, **kwargs)
|
270 |
|
271 |
return token_url, FORM_ENC_HEADERS, body
|
272 |
|
273 |
def prepare_refresh_token_request(self, token_url, refresh_token=None, |
274 |
body='', scope=None, **kwargs): |
275 |
"""Prepare an access token refresh request.
|
276 |
|
277 |
Expired access tokens can be replaced by new access tokens without
|
278 |
going through the OAuth dance if the client obtained a refresh token.
|
279 |
This refresh token and authentication credentials can be used to
|
280 |
obtain a new access token, and possibly a new refresh token.
|
281 |
|
282 |
:param token_url: Provider token refresh endpoint URL.
|
283 |
|
284 |
:param refresh_token: Refresh token string.
|
285 |
|
286 |
:param body: Request body (URL encoded string).
|
287 |
|
288 |
:param scope: List of scopes to request. Must be equal to
|
289 |
or a subset of the scopes granted when obtaining the refresh
|
290 |
token.
|
291 |
|
292 |
:param kwargs: Additional parameters to included in the request.
|
293 |
|
294 |
:returns: The prepared request tuple with (url, headers, body).
|
295 |
"""
|
296 |
if not is_secure_transport(token_url): |
297 |
raise InsecureTransportError()
|
298 |
|
299 |
self.scope = scope or self.scope |
300 |
body = self.prepare_refresh_body(body=body,
|
301 |
refresh_token=refresh_token, scope=self.scope, **kwargs)
|
302 |
return token_url, FORM_ENC_HEADERS, body
|
303 |
|
304 |
def prepare_token_revocation_request(self, revocation_url, token, |
305 |
token_type_hint="access_token", body='', callback=None, **kwargs): |
306 |
"""Prepare a token revocation request.
|
307 |
|
308 |
:param revocation_url: Provider token revocation endpoint URL.
|
309 |
|
310 |
:param token: The access or refresh token to be revoked (string).
|
311 |
|
312 |
:param token_type_hint: ``"access_token"`` (default) or
|
313 |
``"refresh_token"``. This is optional and if you wish to not pass it you
|
314 |
must provide ``token_type_hint=None``.
|
315 |
|
316 |
:param callback: A jsonp callback such as ``package.callback`` to be invoked
|
317 |
upon receiving the response. Not that it should not include a () suffix.
|
318 |
|
319 |
:param kwargs: Additional parameters to included in the request.
|
320 |
|
321 |
:returns: The prepared request tuple with (url, headers, body).
|
322 |
|
323 |
Note that JSONP request may use GET requests as the parameters will
|
324 |
be added to the request URL query as opposed to the request body.
|
325 |
|
326 |
An example of a revocation request
|
327 |
|
328 |
.. code-block: http
|
329 |
|
330 |
POST /revoke HTTP/1.1
|
331 |
Host: server.example.com
|
332 |
Content-Type: application/x-www-form-urlencoded
|
333 |
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
334 |
|
335 |
token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
|
336 |
|
337 |
An example of a jsonp revocation request
|
338 |
|
339 |
.. code-block: http
|
340 |
|
341 |
GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
|
342 |
Host: server.example.com
|
343 |
Content-Type: application/x-www-form-urlencoded
|
344 |
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
|
345 |
|
346 |
and an error response
|
347 |
|
348 |
.. code-block: http
|
349 |
|
350 |
package.myCallback({"error":"unsupported_token_type"});
|
351 |
|
352 |
Note that these requests usually require client credentials, client_id in
|
353 |
the case for public clients and provider specific authentication
|
354 |
credentials for confidential clients.
|
355 |
"""
|
356 |
if not is_secure_transport(revocation_url): |
357 |
raise InsecureTransportError()
|
358 |
|
359 |
return prepare_token_revocation_request(revocation_url, token,
|
360 |
token_type_hint=token_type_hint, body=body, callback=callback, |
361 |
**kwargs) |
362 |
|
363 |
def parse_request_body_response(self, body, scope=None, **kwargs): |
364 |
"""Parse the JSON response body.
|
365 |
|
366 |
If the access token request is valid and authorized, the
|
367 |
authorization server issues an access token as described in
|
368 |
`Section 5.1`_. A refresh token SHOULD NOT be included. If the request
|
369 |
failed client authentication or is invalid, the authorization server
|
370 |
returns an error response as described in `Section 5.2`_.
|
371 |
|
372 |
:param body: The response body from the token request.
|
373 |
:param scope: Scopes originally requested.
|
374 |
:return: Dictionary of token parameters.
|
375 |
:raises: Warning if scope has changed. OAuth2Error if response is invalid.
|
376 |
|
377 |
These response are json encoded and could easily be parsed without
|
378 |
the assistance of OAuthLib. However, there are a few subtle issues
|
379 |
to be aware of regarding the response which are helpfully addressed
|
380 |
through the raising of various errors.
|
381 |
|
382 |
A successful response should always contain
|
383 |
|
384 |
**access_token**
|
385 |
The access token issued by the authorization server. Often
|
386 |
a random string.
|
387 |
|
388 |
**token_type**
|
389 |
The type of the token issued as described in `Section 7.1`_.
|
390 |
Commonly ``Bearer``.
|
391 |
|
392 |
While it is not mandated it is recommended that the provider include
|
393 |
|
394 |
**expires_in**
|
395 |
The lifetime in seconds of the access token. For
|
396 |
example, the value "3600" denotes that the access token will
|
397 |
expire in one hour from the time the response was generated.
|
398 |
If omitted, the authorization server SHOULD provide the
|
399 |
expiration time via other means or document the default value.
|
400 |
|
401 |
**scope**
|
402 |
Providers may supply this in all responses but are required to only
|
403 |
if it has changed since the authorization request.
|
404 |
|
405 |
.. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
|
406 |
.. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
|
407 |
.. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
|
408 |
"""
|
409 |
self.token = parse_token_response(body, scope=scope)
|
410 |
self._populate_attributes(self.token) |
411 |
return self.token |
412 |
|
413 |
def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs): |
414 |
"""Prepare an access token request, using a refresh token.
|
415 |
|
416 |
If the authorization server issued a refresh token to the client, the
|
417 |
client makes a refresh request to the token endpoint by adding the
|
418 |
following parameters using the "application/x-www-form-urlencoded"
|
419 |
format in the HTTP request entity-body:
|
420 |
|
421 |
grant_type
|
422 |
REQUIRED. Value MUST be set to "refresh_token".
|
423 |
refresh_token
|
424 |
REQUIRED. The refresh token issued to the client.
|
425 |
scope
|
426 |
OPTIONAL. The scope of the access request as described by
|
427 |
Section 3.3. The requested scope MUST NOT include any scope
|
428 |
not originally granted by the resource owner, and if omitted is
|
429 |
treated as equal to the scope originally granted by the
|
430 |
resource owner.
|
431 |
"""
|
432 |
refresh_token = refresh_token or self.refresh_token |
433 |
return prepare_token_request('refresh_token', body=body, scope=scope, |
434 |
refresh_token=refresh_token, **kwargs) |
435 |
|
436 |
def _add_bearer_token(self, uri, http_method='GET', body=None, |
437 |
headers=None, token_placement=None): |
438 |
"""Add a bearer token to the request uri, body or authorization header."""
|
439 |
if token_placement == AUTH_HEADER:
|
440 |
headers = tokens.prepare_bearer_headers(self.access_token, headers)
|
441 |
|
442 |
elif token_placement == URI_QUERY:
|
443 |
uri = tokens.prepare_bearer_uri(self.access_token, uri)
|
444 |
|
445 |
elif token_placement == BODY:
|
446 |
body = tokens.prepare_bearer_body(self.access_token, body)
|
447 |
|
448 |
else:
|
449 |
raise ValueError("Invalid token placement.") |
450 |
return uri, headers, body
|
451 |
|
452 |
def _add_mac_token(self, uri, http_method='GET', body=None, |
453 |
headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs): |
454 |
"""Add a MAC token to the request authorization header.
|
455 |
|
456 |
Warning: MAC token support is experimental as the spec is not yet stable.
|
457 |
"""
|
458 |
headers = tokens.prepare_mac_header(self.access_token, uri,
|
459 |
self.mac_key, http_method, headers=headers, body=body, ext=ext,
|
460 |
hash_algorithm=self.mac_algorithm, **kwargs)
|
461 |
return uri, headers, body
|
462 |
|
463 |
def _populate_attributes(self, response): |
464 |
"""Add commonly used values such as access_token to self."""
|
465 |
|
466 |
if 'access_token' in response: |
467 |
self.access_token = response.get('access_token') |
468 |
|
469 |
if 'refresh_token' in response: |
470 |
self.refresh_token = response.get('refresh_token') |
471 |
|
472 |
if 'token_type' in response: |
473 |
self.token_type = response.get('token_type') |
474 |
|
475 |
if 'expires_in' in response: |
476 |
self.expires_in = response.get('expires_in') |
477 |
self._expires_at = time.time() + int(self.expires_in) |
478 |
|
479 |
if 'expires_at' in response: |
480 |
self._expires_at = int(response.get('expires_at')) |
481 |
|
482 |
if 'code' in response: |
483 |
self.code = response.get('code') |
484 |
|
485 |
if 'mac_key' in response: |
486 |
self.mac_key = response.get('mac_key') |
487 |
|
488 |
if 'mac_algorithm' in response: |
489 |
self.mac_algorithm = response.get('mac_algorithm') |
490 |
|