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 / tokens.py @ 564
History | View | Annotate | Download (9.09 KB)
1 |
"""
|
---|---|
2 |
oauthlib.oauth2.rfc6749.tokens
|
3 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
4 |
|
5 |
This module contains methods for adding two types of access tokens to requests.
|
6 |
|
7 |
- Bearer http://tools.ietf.org/html/rfc6750
|
8 |
- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
9 |
"""
|
10 |
from __future__ import absolute_import, unicode_literals |
11 |
|
12 |
from binascii import b2a_base64 |
13 |
import hashlib |
14 |
import hmac |
15 |
try:
|
16 |
from urlparse import urlparse |
17 |
except ImportError: |
18 |
from urllib.parse import urlparse |
19 |
|
20 |
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type |
21 |
from oauthlib import common |
22 |
|
23 |
from . import utils
|
24 |
|
25 |
|
26 |
class OAuth2Token(dict): |
27 |
|
28 |
def __init__(self, params, old_scope=None): |
29 |
super(OAuth2Token, self).__init__(params) |
30 |
self._new_scope = None |
31 |
if 'scope' in params: |
32 |
self._new_scope = set(utils.scope_to_list(params['scope'])) |
33 |
if old_scope is not None: |
34 |
self._old_scope = set(utils.scope_to_list(old_scope)) |
35 |
if self._new_scope is None: |
36 |
# the rfc says that if the scope hasn't changed, it's optional
|
37 |
# in params so set the new scope to the old scope
|
38 |
self._new_scope = self._old_scope |
39 |
else:
|
40 |
self._old_scope = self._new_scope |
41 |
|
42 |
@property
|
43 |
def scope_changed(self): |
44 |
return self._new_scope != self._old_scope |
45 |
|
46 |
@property
|
47 |
def old_scope(self): |
48 |
return utils.list_to_scope(self._old_scope) |
49 |
|
50 |
@property
|
51 |
def old_scopes(self): |
52 |
return list(self._old_scope) |
53 |
|
54 |
@property
|
55 |
def scope(self): |
56 |
return utils.list_to_scope(self._new_scope) |
57 |
|
58 |
@property
|
59 |
def scopes(self): |
60 |
return list(self._new_scope) |
61 |
|
62 |
@property
|
63 |
def missing_scopes(self): |
64 |
return list(self._old_scope - self._new_scope) |
65 |
|
66 |
@property
|
67 |
def additional_scopes(self): |
68 |
return list(self._new_scope - self._old_scope) |
69 |
|
70 |
|
71 |
def prepare_mac_header(token, uri, key, http_method, |
72 |
nonce=None,
|
73 |
headers=None,
|
74 |
body=None,
|
75 |
ext='',
|
76 |
hash_algorithm='hmac-sha-1',
|
77 |
issue_time=None,
|
78 |
draft=0):
|
79 |
"""Add an `MAC Access Authentication`_ signature to headers.
|
80 |
|
81 |
Unlike OAuth 1, this HMAC signature does not require inclusion of the
|
82 |
request payload/body, neither does it use a combination of client_secret
|
83 |
and token_secret but rather a mac_key provided together with the access
|
84 |
token.
|
85 |
|
86 |
Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
|
87 |
`extension algorithms`_ are not supported.
|
88 |
|
89 |
Example MAC Authorization header, linebreaks added for clarity
|
90 |
|
91 |
Authorization: MAC id="h480djs93hd8",
|
92 |
nonce="1336363200:dj83hs9s",
|
93 |
mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
|
94 |
|
95 |
.. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
|
96 |
.. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
|
97 |
|
98 |
:param uri: Request URI.
|
99 |
:param headers: Request headers as a dictionary.
|
100 |
:param http_method: HTTP Request method.
|
101 |
:param key: MAC given provided by token endpoint.
|
102 |
:param hash_algorithm: HMAC algorithm provided by token endpoint.
|
103 |
:param issue_time: Time when the MAC credentials were issued (datetime).
|
104 |
:param draft: MAC authentication specification version.
|
105 |
:return: headers dictionary with the authorization field added.
|
106 |
"""
|
107 |
http_method = http_method.upper() |
108 |
host, port = utils.host_from_uri(uri) |
109 |
|
110 |
if hash_algorithm.lower() == 'hmac-sha-1': |
111 |
h = hashlib.sha1 |
112 |
elif hash_algorithm.lower() == 'hmac-sha-256': |
113 |
h = hashlib.sha256 |
114 |
else:
|
115 |
raise ValueError('unknown hash algorithm') |
116 |
|
117 |
if draft == 0: |
118 |
nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time), |
119 |
common.generate_nonce()) |
120 |
else:
|
121 |
ts = common.generate_timestamp() |
122 |
nonce = common.generate_nonce() |
123 |
|
124 |
sch, net, path, par, query, fra = urlparse(uri) |
125 |
|
126 |
if query:
|
127 |
request_uri = path + '?' + query
|
128 |
else:
|
129 |
request_uri = path |
130 |
|
131 |
# Hash the body/payload
|
132 |
if body is not None and draft == 0: |
133 |
body = body.encode('utf-8')
|
134 |
bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8') |
135 |
else:
|
136 |
bodyhash = ''
|
137 |
|
138 |
# Create the normalized base string
|
139 |
base = [] |
140 |
if draft == 0: |
141 |
base.append(nonce) |
142 |
else:
|
143 |
base.append(ts) |
144 |
base.append(nonce) |
145 |
base.append(http_method.upper()) |
146 |
base.append(request_uri) |
147 |
base.append(host) |
148 |
base.append(port) |
149 |
if draft == 0: |
150 |
base.append(bodyhash) |
151 |
base.append(ext or '') |
152 |
base_string = '\n'.join(base) + '\n' |
153 |
|
154 |
# hmac struggles with unicode strings - http://bugs.python.org/issue5285
|
155 |
if isinstance(key, unicode_type): |
156 |
key = key.encode('utf-8')
|
157 |
sign = hmac.new(key, base_string.encode('utf-8'), h)
|
158 |
sign = b2a_base64(sign.digest())[:-1].decode('utf-8') |
159 |
|
160 |
header = [] |
161 |
header.append('MAC id="%s"' % token)
|
162 |
if draft != 0: |
163 |
header.append('ts="%s"' % ts)
|
164 |
header.append('nonce="%s"' % nonce)
|
165 |
if bodyhash:
|
166 |
header.append('bodyhash="%s"' % bodyhash)
|
167 |
if ext:
|
168 |
header.append('ext="%s"' % ext)
|
169 |
header.append('mac="%s"' % sign)
|
170 |
|
171 |
headers = headers or {}
|
172 |
headers['Authorization'] = ', '.join(header) |
173 |
return headers
|
174 |
|
175 |
|
176 |
def prepare_bearer_uri(token, uri): |
177 |
"""Add a `Bearer Token`_ to the request URI.
|
178 |
Not recommended, use only if client can't use authorization header or body.
|
179 |
|
180 |
http://www.example.com/path?access_token=h480djs93hd8
|
181 |
|
182 |
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
183 |
"""
|
184 |
return add_params_to_uri(uri, [(('access_token', token))]) |
185 |
|
186 |
|
187 |
def prepare_bearer_headers(token, headers=None): |
188 |
"""Add a `Bearer Token`_ to the request URI.
|
189 |
Recommended method of passing bearer tokens.
|
190 |
|
191 |
Authorization: Bearer h480djs93hd8
|
192 |
|
193 |
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
194 |
"""
|
195 |
headers = headers or {}
|
196 |
headers['Authorization'] = 'Bearer %s' % token |
197 |
return headers
|
198 |
|
199 |
|
200 |
def prepare_bearer_body(token, body=''): |
201 |
"""Add a `Bearer Token`_ to the request body.
|
202 |
|
203 |
access_token=h480djs93hd8
|
204 |
|
205 |
.. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
|
206 |
"""
|
207 |
return add_params_to_qs(body, [(('access_token', token))]) |
208 |
|
209 |
|
210 |
def random_token_generator(request, refresh_token=False): |
211 |
return common.generate_token()
|
212 |
|
213 |
|
214 |
def signed_token_generator(private_pem, **kwargs): |
215 |
def signed_token_generator(request): |
216 |
request.claims = kwargs |
217 |
return common.generate_signed_token(private_pem, request)
|
218 |
|
219 |
return signed_token_generator
|
220 |
|
221 |
|
222 |
class TokenBase(object): |
223 |
|
224 |
def __call__(self, request, refresh_token=False): |
225 |
raise NotImplementedError('Subclasses must implement this method.') |
226 |
|
227 |
def validate_request(self, request): |
228 |
raise NotImplementedError('Subclasses must implement this method.') |
229 |
|
230 |
def estimate_type(self, request): |
231 |
raise NotImplementedError('Subclasses must implement this method.') |
232 |
|
233 |
|
234 |
class BearerToken(TokenBase): |
235 |
|
236 |
def __init__(self, request_validator=None, token_generator=None, |
237 |
expires_in=None, refresh_token_generator=None): |
238 |
self.request_validator = request_validator
|
239 |
self.token_generator = token_generator or random_token_generator |
240 |
self.refresh_token_generator = (
|
241 |
refresh_token_generator or self.token_generator |
242 |
) |
243 |
self.expires_in = expires_in or 3600 |
244 |
|
245 |
def create_token(self, request, refresh_token=False): |
246 |
"""Create a BearerToken, by default without refresh token."""
|
247 |
|
248 |
if callable(self.expires_in): |
249 |
expires_in = self.expires_in(request)
|
250 |
else:
|
251 |
expires_in = self.expires_in
|
252 |
|
253 |
request.expires_in = expires_in |
254 |
|
255 |
token = { |
256 |
'access_token': self.token_generator(request), |
257 |
'expires_in': expires_in,
|
258 |
'token_type': 'Bearer', |
259 |
} |
260 |
|
261 |
if request.scopes is not None: |
262 |
token['scope'] = ' '.join(request.scopes) |
263 |
|
264 |
if request.state is not None: |
265 |
token['state'] = request.state
|
266 |
|
267 |
if refresh_token:
|
268 |
if (request.refresh_token and |
269 |
not self.request_validator.rotate_refresh_token(request)): |
270 |
token['refresh_token'] = request.refresh_token
|
271 |
else:
|
272 |
token['refresh_token'] = self.refresh_token_generator(request) |
273 |
|
274 |
token.update(request.extra_credentials or {})
|
275 |
token = OAuth2Token(token) |
276 |
self.request_validator.save_bearer_token(token, request)
|
277 |
return token
|
278 |
|
279 |
def validate_request(self, request): |
280 |
token = None
|
281 |
if 'Authorization' in request.headers: |
282 |
token = request.headers.get('Authorization')[7:] |
283 |
else:
|
284 |
token = request.access_token |
285 |
return self.request_validator.validate_bearer_token( |
286 |
token, request.scopes, request) |
287 |
|
288 |
def estimate_type(self, request): |
289 |
if request.headers.get('Authorization', '').startswith('Bearer'): |
290 |
return 9 |
291 |
elif request.access_token is not None: |
292 |
return 5 |
293 |
else:
|
294 |
return 0 |