diff options
author | Lukáš Lalinský <lalinsky@gmail.com> | 2012-11-10 15:17:06 +0100 |
---|---|---|
committer | Lukáš Lalinský <lalinsky@gmail.com> | 2012-11-10 15:17:06 +0100 |
commit | 680459a1b9e42fab645138a587a218a77a8234d7 (patch) | |
tree | 756bcd25c7b3d72f635b716f782f4ea5e1ddbb70 | |
parent | f0f47aa2dcd5812a8e45295531f3d74bfb609313 (diff) | |
download | oauthlib-680459a1b9e42fab645138a587a218a77a8234d7.tar.gz |
Add support for v01 MAC tokens
-rw-r--r-- | oauthlib/oauth2/draft25/__init__.py | 22 | ||||
-rw-r--r-- | oauthlib/oauth2/draft25/tokens.py | 34 | ||||
-rw-r--r-- | oauthlib/oauth2/draft25/utils.py | 9 | ||||
-rw-r--r-- | tests/oauth2/draft25/test_client.py | 51 |
4 files changed, 103 insertions, 13 deletions
diff --git a/oauthlib/oauth2/draft25/__init__.py b/oauthlib/oauth2/draft25/__init__.py index 99794a9..e224337 100644 --- a/oauthlib/oauth2/draft25/__init__.py +++ b/oauthlib/oauth2/draft25/__init__.py @@ -32,6 +32,8 @@ class Client(object): token_type='Bearer', access_token=None, refresh_token=None, + mac_key=None, + mac_algorithm=None, **kwargs): """Initialize a client with commonly used attributes.""" @@ -40,6 +42,8 @@ class Client(object): self.token_type = token_type self.access_token = access_token self.refresh_token = refresh_token + self.mac_key = mac_key + self.mac_algorithm = mac_algorithm @property def token_types(self): @@ -58,7 +62,7 @@ class Client(object): } def add_token(self, uri, http_method='GET', body=None, headers=None, - token_placement=None): + token_placement=None, **kwargs): """Add token to the request uri, body or authorization header. The access token type provides the client with the information @@ -97,7 +101,7 @@ class Client(object): raise ValueError("Missing access token.") return self.token_types[self.token_type](uri, http_method, body, - headers, token_placement) + headers, token_placement, **kwargs) def prepare_refresh_body(self, body='', refresh_token=None, scope=None): """Prepare an access token request, using a refresh token. @@ -139,14 +143,14 @@ class Client(object): return uri, headers, body def _add_mac_token(self, uri, http_method='GET', body=None, - headers=None, token_placement=AUTH_HEADER): + headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs): """Add a MAC token to the request authorization header. Warning: MAC token support is experimental as the spec is not yet stable. """ - headers = prepare_mac_header(self.access_token, uri, self.key, http_method, - headers=headers, body=body, ext=self.ext, - hash_algorithm=self.hash_algorithm) + headers = prepare_mac_header(self.access_token, uri, self.mac_key, http_method, + headers=headers, body=body, ext=ext, + hash_algorithm=self.mac_algorithm, **kwargs) return uri, headers, body def _populate_attributes(self, response): @@ -167,6 +171,12 @@ class Client(object): if 'code' in response: self.code = response.get('code') + if 'mac_key' in response: + self.mac_key = response.get('mac_key') + + if 'mac_algorithm' in response: + self.mac_algorithm = response.get('mac_algorithm') + def prepare_request_uri(self, *args, **kwargs): """Abstract method used to create request URIs.""" raise NotImplementedError("Must be implemented by inheriting classes.") diff --git a/oauthlib/oauth2/draft25/tokens.py b/oauthlib/oauth2/draft25/tokens.py index 1fc719f..cb58036 100644 --- a/oauthlib/oauth2/draft25/tokens.py +++ b/oauthlib/oauth2/draft25/tokens.py @@ -10,6 +10,7 @@ This module contains methods for adding two types of access tokens to requests. """ from binascii import b2a_base64 +import datetime import hashlib import hmac try: @@ -18,11 +19,13 @@ except ImportError: from urllib.parse import urlparse from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type +from oauthlib import common from . import utils def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None, - body=None, ext='', hash_algorithm='hmac-sha-1'): + body=None, ext='', hash_algorithm='hmac-sha-1', issue_time=None, + draft=0): """Add an `MAC Access Authentication`_ signature to headers. Unlike OAuth 1, this HMAC signature does not require inclusion of the request @@ -46,6 +49,8 @@ def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None, :param http_method: HTTP Request method. :param key: MAC given provided by token endpoint. :param algorithm: HMAC algorithm provided by token endpoint. + :param issue_time: Time when the MAC credentials were issues as a datetime object. + :param draft: MAC authentication specification version. :return: headers dictionary with the authorization field added. """ http_method = http_method.upper() @@ -53,10 +58,18 @@ def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None, if hash_algorithm.lower() == 'hmac-sha-1': h = hashlib.sha1 - else: + elif hash_algorithm.lower() == 'hmac-sha-256': h = hashlib.sha256 + else: + raise ValueError('unknown hash algorithm') + + if draft == 0: + nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time), + common.generate_nonce()) + else: + ts = common.generate_timestamp() + nonce = common.generate_nonce() - nonce = nonce or '{0}:{1}'.format(utils.generate_nonce(), utils.generate_timestamp()) sch, net, path, par, query, fra = urlparse(uri) if query: @@ -65,20 +78,25 @@ def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None, request_uri = path # Hash the body/payload - if body is not None: + if body is not None and draft == 0: bodyhash = b2a_base64(h(body.encode('utf-8')).digest())[:-1].decode('utf-8') else: bodyhash = '' # Create the normalized base string base = [] - base.append(nonce) + if draft == 0: + base.append(nonce) + else: + base.append(ts) + base.append(nonce) base.append(http_method.upper()) base.append(request_uri) base.append(host) base.append(port) - base.append(bodyhash) - base.append(ext) + if draft == 0: + base.append(bodyhash) + base.append(ext or '') base_string = '\n'.join(base) + '\n' # hmac struggles with unicode strings - http://bugs.python.org/issue5285 @@ -89,6 +107,8 @@ def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None, header = [] header.append('MAC id="%s"' % token) + if draft != 0: + header.append('ts="%s"' % ts) header.append('nonce="%s"' % nonce) if bodyhash: header.append('bodyhash="%s"' % bodyhash) diff --git a/oauthlib/oauth2/draft25/utils.py b/oauthlib/oauth2/draft25/utils.py index fdc4859..82cd8a0 100644 --- a/oauthlib/oauth2/draft25/utils.py +++ b/oauthlib/oauth2/draft25/utils.py @@ -8,6 +8,7 @@ oauthlib.utils This module contains utility methods used by various parts of the OAuth 2 spec. """ +import datetime try: from urllib import quote except ImportError: @@ -65,3 +66,11 @@ def escape(u): if not isinstance(u, unicode_type): raise ValueError('Only unicode objects are escapable.') return quote(u.encode('utf-8'), safe=b'~') + + +def generate_age(issue_time): + """Generate a age parameter for MAC authentication draft 00.""" + td = datetime.datetime.now() - issue_time + age = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + return unicode_type(age) + diff --git a/tests/oauth2/draft25/test_client.py b/tests/oauth2/draft25/test_client.py index 63e2f92..ceb3d3b 100644 --- a/tests/oauth2/draft25/test_client.py +++ b/tests/oauth2/draft25/test_client.py @@ -2,6 +2,8 @@ from __future__ import absolute_import, unicode_literals from ...unittest import TestCase +import datetime +from oauthlib import common from oauthlib.oauth2.draft25 import Client, PasswordCredentialsClient from oauthlib.oauth2.draft25 import UserAgentClient, WebApplicationClient from oauthlib.oauth2.draft25 import ClientCredentialsClient @@ -15,6 +17,7 @@ class ClientTest(TestCase): body = "not=empty" headers = {} access_token = "token" + mac_key = "secret" bearer_query = uri + "&access_token=" + access_token bearer_header = { @@ -22,6 +25,16 @@ class ClientTest(TestCase): } bearer_body = body + "&access_token=" + access_token + mac_00_header = { + "Authorization": 'MAC id="' + access_token + '", nonce="0:abc123",' + + ' bodyhash="Yqyso8r3hR5Nm1ZFv+6AvNHrxjE=",' + + ' mac="khWygG6wFPnWeJteDP7aLOPgzZM="' + } + mac_01_header = { + "Authorization": 'MAC id="' + access_token + '", ts="123456789",' + + ' nonce="abc123", mac="CoHLzBGb8zVNdLZQDA2tiO6mryk="' + } + def test_add_bearer_token(self): """Test a number of bearer token placements""" @@ -98,6 +111,44 @@ class ClientTest(TestCase): self.assertRaises(ValueError, client.add_token, self.uri, body=self.body, headers=self.headers) + def test_add_mac_token(self): + # Missing access token + client = Client(self.client_id, token_type="MAC") + self.assertRaises(ValueError, client.add_token, self.uri) + + # Invalid hash algorithm + client = Client(self.client_id, token_type="MAC", + access_token=self.access_token, mac_key=self.mac_key, + mac_algorithm="hmac-sha-2") + self.assertRaises(ValueError, client.add_token, self.uri) + + orig_generate_timestamp = common.generate_timestamp + orig_generate_nonce = common.generate_nonce + self.addCleanup(setattr, common, 'generage_timestamp', orig_generate_timestamp) + self.addCleanup(setattr, common, 'generage_nonce', orig_generate_nonce) + common.generate_timestamp = lambda: '123456789' + common.generate_nonce = lambda: 'abc123' + + # Add the Authorization header (draft 00) + client = Client(self.client_id, token_type="MAC", + access_token=self.access_token, mac_key=self.mac_key, + mac_algorithm="hmac-sha-1") + uri, headers, body = client.add_token(self.uri, body=self.body, + headers=self.headers, issue_time=datetime.datetime.now()) + self.assertEqual(uri, self.uri) + self.assertEqual(body, self.body) + self.assertEqual(headers, self.mac_00_header) + + # Add the Authorization header (draft 00) + client = Client(self.client_id, token_type="MAC", + access_token=self.access_token, mac_key=self.mac_key, + mac_algorithm="hmac-sha-1") + uri, headers, body = client.add_token(self.uri, body=self.body, + headers=self.headers, draft=1) + self.assertEqual(uri, self.uri) + self.assertEqual(body, self.body) + self.assertEqual(headers, self.mac_01_header) + class WebApplicationClientTest(TestCase): |