2 oauthlib.oauth2.rfc6749.tokens
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 This module contains methods for adding two types of access tokens to requests.
7 - Bearer http://tools.ietf.org/html/rfc6750
8 - MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
10 from __future__ import absolute_import, unicode_literals
12 from binascii import b2a_base64
16 from urlparse import urlparse
18 from urllib.parse import urlparse
20 from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
21 from oauthlib import common
26 class OAuth2Token(dict):
28 def __init__(self, params, old_scope=None):
29 super(OAuth2Token, self).__init__(params)
30 self._new_scope = None
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
40 self._old_scope = self._new_scope
43 def scope_changed(self):
44 return self._new_scope != self._old_scope
48 return utils.list_to_scope(self._old_scope)
52 return list(self._old_scope)
56 return utils.list_to_scope(self._new_scope)
60 return list(self._new_scope)
63 def missing_scopes(self):
64 return list(self._old_scope - self._new_scope)
67 def additional_scopes(self):
68 return list(self._new_scope - self._old_scope)
71 def prepare_mac_header(token, uri, key, http_method,
76 hash_algorithm='hmac-sha-1',
79 """Add an `MAC Access Authentication`_ signature to headers.
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
86 Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
87 `extension algorithms`_ are not supported.
89 Example MAC Authorization header, linebreaks added for clarity
91 Authorization: MAC id="h480djs93hd8",
92 nonce="1336363200:dj83hs9s",
93 mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
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
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.
107 http_method = http_method.upper()
108 host, port = utils.host_from_uri(uri)
110 if hash_algorithm.lower() == 'hmac-sha-1':
112 elif hash_algorithm.lower() == 'hmac-sha-256':
115 raise ValueError('unknown hash algorithm')
118 nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time),
119 common.generate_nonce())
121 ts = common.generate_timestamp()
122 nonce = common.generate_nonce()
124 sch, net, path, par, query, fra = urlparse(uri)
127 request_uri = path + '?' + query
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')
138 # Create the normalized base string
145 base.append(http_method.upper())
146 base.append(request_uri)
150 base.append(bodyhash)
151 base.append(ext or '')
152 base_string = '\n'.join(base) + '\n'
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')
161 header.append('MAC id="%s"' % token)
163 header.append('ts="%s"' % ts)
164 header.append('nonce="%s"' % nonce)
166 header.append('bodyhash="%s"' % bodyhash)
168 header.append('ext="%s"' % ext)
169 header.append('mac="%s"' % sign)
171 headers = headers or {}
172 headers['Authorization'] = ', '.join(header)
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.
180 http://www.example.com/path?access_token=h480djs93hd8
182 .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
184 return add_params_to_uri(uri, [(('access_token', token))])
187 def prepare_bearer_headers(token, headers=None):
188 """Add a `Bearer Token`_ to the request URI.
189 Recommended method of passing bearer tokens.
191 Authorization: Bearer h480djs93hd8
193 .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
195 headers = headers or {}
196 headers['Authorization'] = 'Bearer %s' % token
200 def prepare_bearer_body(token, body=''):
201 """Add a `Bearer Token`_ to the request body.
203 access_token=h480djs93hd8
205 .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
207 return add_params_to_qs(body, [(('access_token', token))])
210 def random_token_generator(request, refresh_token=False):
211 return common.generate_token()
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)
219 return signed_token_generator
222 class TokenBase(object):
224 def __call__(self, request, refresh_token=False):
225 raise NotImplementedError('Subclasses must implement this method.')
227 def validate_request(self, request):
228 raise NotImplementedError('Subclasses must implement this method.')
230 def estimate_type(self, request):
231 raise NotImplementedError('Subclasses must implement this method.')
234 class BearerToken(TokenBase):
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
243 self.expires_in = expires_in or 3600
245 def create_token(self, request, refresh_token=False):
246 """Create a BearerToken, by default without refresh token."""
248 if callable(self.expires_in):
249 expires_in = self.expires_in(request)
251 expires_in = self.expires_in
253 request.expires_in = expires_in
256 'access_token': self.token_generator(request),
257 'expires_in': expires_in,
258 'token_type': 'Bearer',
261 if request.scopes is not None:
262 token['scope'] = ' '.join(request.scopes)
264 if request.state is not None:
265 token['state'] = request.state
268 if (request.refresh_token and
269 not self.request_validator.rotate_refresh_token(request)):
270 token['refresh_token'] = request.refresh_token
272 token['refresh_token'] = self.refresh_token_generator(request)
274 token.update(request.extra_credentials or {})
275 token = OAuth2Token(token)
276 self.request_validator.save_bearer_token(token, request)
279 def validate_request(self, request):
281 if 'Authorization' in request.headers:
282 token = request.headers.get('Authorization')[7:]
284 token = request.access_token
285 return self.request_validator.validate_bearer_token(
286 token, request.scopes, request)
288 def estimate_type(self, request):
289 if request.headers.get('Authorization', '').startswith('Bearer'):
291 elif request.access_token is not None: