diff options
author | Idan Gazit <idan@gazit.me> | 2012-04-11 02:47:11 -0700 |
---|---|---|
committer | Idan Gazit <idan@gazit.me> | 2012-04-11 02:47:11 -0700 |
commit | 89c685be55e5656385e36d670f2e6ab19ce09696 (patch) | |
tree | 48bdc1c739d452a99c39b3c3e029c2874ab36582 | |
parent | 018719878738cf3432a1398cffae29a9f781b327 (diff) | |
parent | cb7587f7674db5d00df3a6134ad815fa03e2e7e6 (diff) | |
download | oauthlib-89c685be55e5656385e36d670f2e6ab19ce09696.tar.gz |
Merge pull request #17 from ib-lundgren/oauth2_tokens
Bearer and MAC token support
-rw-r--r-- | oauthlib/oauth2_draft25/tokens.py | 126 | ||||
-rw-r--r-- | tests/oauth2_draft25/test_tokens.py | 84 |
2 files changed, 210 insertions, 0 deletions
diff --git a/oauthlib/oauth2_draft25/tokens.py b/oauthlib/oauth2_draft25/tokens.py new file mode 100644 index 0000000..ed779c7 --- /dev/null +++ b/oauthlib/oauth2_draft25/tokens.py @@ -0,0 +1,126 @@ +from __future__ import absolute_import +""" +oauthlib.oauth2_draft25.tokens +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains methods for adding two types of access tokens to requests. + +- Bearer http://tools.ietf.org/html/draft-ietf-oauth-saml2-bearer-08 +- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-00 + +""" +from binascii import b2a_base64 +import hashlib +import hmac +import time +from urlparse import urlparse + +from . import utils + +def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None, + body=None, ext=u'', hash_algorithm=u'hmac-sha-1'): + """Add an `MAC Access Authentication`_ signature to headers. + + Unlike OAuth 1, this HMAC signature does not require inclusion of the request + payload/body, neither does it use a combination of client_secret and + token_secret but rather a mac_key provided together with the access token. + + Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256", + `extension algorithms`_ are not supported. + + Example MAC Authorization header, linebreaks added for clarity + + Authorization: MAC id="h480djs93hd8", + nonce="1336363200:dj83hs9s", + mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM=" + + .. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 + .. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1 + + :param uri: Request URI. + :param headers: Request headers as a dictionary. + :param http_method: HTTP Request method. + :param key: MAC given provided by token endpoint. + :param algorithm: HMAC algorithm provided by token endpoint. + :return: headers dictionary with the authorization field added. + """ + http_method = http_method.upper() + host, port = utils.host_from_uri(uri) + + if hash_algorithm.lower() == u'hmac-sha-1': + h = hashlib.sha1 + else: + h = hashlib.sha256 + + nonce = nonce or u'{}:{}'.format(utils.generate_nonce(), utils.generate_timestamp()) + sch, net, path, par, query, fra = urlparse(uri) + + if query: + request_uri = path + u'?' + query + else: + request_uri = path + + # Hash the body/payload + if body is not None: + bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8') + else: + bodyhash = u'' + + # Create the normalized base string + base = [] + base.append(nonce) + base.append(http_method.upper()) + base.append(request_uri) + base.append(host) + base.append(port) + base.append(bodyhash) + base.append(ext) + base_string = '\n'.join(base) + u'\n' + + # hmac struggles with unicode strings - http://bugs.python.org/issue5285 + if isinstance(key, unicode): + key = key.encode('utf-8') + sign = hmac.new(key, base_string, h) + sign = b2a_base64(sign.digest())[:-1].decode('utf-8') + + header = [] + header.append(u'MAC id="%s"' % token) + header.append(u'nonce="%s"' % nonce) + if bodyhash: header.append(u'bodyhash="%s"' % bodyhash) + if ext: header.append(u'ext="%s"' % ext) + header.append(u'mac="%s"' % sign) + + headers = headers or {} + headers[u'Authorization'] = u', '.join(header) + return headers + +def prepare_bearer_uri(token, uri): + """Add a `Bearer Token`_ to the request URI. + Not recommended, use only if client can't use authorization header or body. + + http://www.example.com/path?access_token=h480djs93hd8 + + .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18 + """ + return utils.add_params_to_uri(uri, [((u'access_token', token))]) + +def prepare_bearer_headers(token, headers=None): + """Add a `Bearer Token`_ to the request URI. + Recommended method of passing bearer tokens. + + Authorization: Bearer h480djs93hd8 + + .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18 + """ + headers = headers or {} + headers[u'Authorization'] = u'Bearer %s' % token + return headers + +def prepare_bearer_body(token, body=u''): + """Add a `Bearer Token`_ to the request body. + + access_token=h480djs93hd8 + + .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18 + """ + return utils.add_params_to_qs(body, [((u'access_token', token))]) diff --git a/tests/oauth2_draft25/test_tokens.py b/tests/oauth2_draft25/test_tokens.py new file mode 100644 index 0000000..d605330 --- /dev/null +++ b/tests/oauth2_draft25/test_tokens.py @@ -0,0 +1,84 @@ +from __future__ import absolute_import + +from ..unittest import TestCase + +from oauthlib.oauth2_draft25.tokens import * + +class TokenTest(TestCase): + + # MAC without body/payload or extension + mac_plain = { + u'token' : u'h480djs93hd8', + u'uri' : u'http://example.com/resource/1?b=1&a=2', + u'key' : u'489dks293j39', + u'http_method' : u'GET', + u'nonce' : u'264095:dj83hs9s', + u'hash_algorithm' : u'hmac-sha-1' + } + auth_plain = { + u'Authorization' : u'MAC id="h480djs93hd8", nonce="264095:dj83hs9s",' + ' mac="SLDJd4mg43cjQfElUs3Qub4L6xE="' + } + + # MAC with body/payload, no extension + mac_body = { + u'token' : u'jd93dh9dh39D', + u'uri' : u'http://example.com/request', + u'key' : u'8yfrufh348h', + u'http_method' : u'POST', + u'nonce' : u'273156:di3hvdf8', + u'hash_algorithm' : u'hmac-sha-1', + u'body' : u'hello=world%21' + } + auth_body = { + u'Authorization' : u'MAC id="jd93dh9dh39D", nonce="273156:di3hvdf8",' + ' bodyhash="k9kbtCIy0CkI3/FEfpS/oIDjk6k=", mac="W7bdMZbv9UWOTadASIQHagZyirA="' + } + + # MAC with body/payload and extension + mac_both = { + u'token' : u'h480djs93hd8', + u'uri' : u'http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b&c2&a3=2+q', + u'key' : u'489dks293j39', + u'http_method' : u'GET', + u'nonce' : u'264095:7d8f3e4a', + u'hash_algorithm' : u'hmac-sha-1', + u'body' : u'Hello World!', + u'ext' : u'a,b,c' + } + auth_both = { + u'Authorization' : u'MAC id="h480djs93hd8", nonce="264095:7d8f3e4a",' + ' bodyhash="Lve95gjOVATpfV8EL5X4nxwjKHE=", ext="a,b,c",' + ' mac="Z3C2DojEopRDIC88/imW8Ez853g="' + } + + + # Bearer + token = u'vF9dft4qmT' + uri = u'http://server.example.com/resource' + bearer_headers = { + u'Authorization' : u'Bearer vF9dft4qmT' + } + bearer_body = u'access_token=vF9dft4qmT' + bearer_uri = u'http://server.example.com/resource?access_token=vF9dft4qmT' + + + def test_prepare_mac_header(self): + """Verify mac signatures correctness + + TODO: verify hmac-sha-256 + """ + self.assertEqual(prepare_mac_header(**self.mac_plain), self.auth_plain) + self.assertEqual(prepare_mac_header(**self.mac_body), self.auth_body) + self.assertEqual(prepare_mac_header(**self.mac_both), self.auth_both) + + + def test_prepare_bearer_request(self): + """Verify proper addition of bearer tokens to requests. + + They may be represented as query components in body or URI or + in a Bearer authorization header. + """ + self.assertEqual(prepare_bearer_headers(self.token), self.bearer_headers) + self.assertEqual(prepare_bearer_body(self.token), self.bearer_body) + self.assertEqual(prepare_bearer_uri(self.token, uri=self.uri), self.bearer_uri) |