summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukáš Lalinský <lalinsky@gmail.com>2012-11-10 15:17:06 +0100
committerLukáš Lalinský <lalinsky@gmail.com>2012-11-10 15:17:06 +0100
commit680459a1b9e42fab645138a587a218a77a8234d7 (patch)
tree756bcd25c7b3d72f635b716f782f4ea5e1ddbb70
parentf0f47aa2dcd5812a8e45295531f3d74bfb609313 (diff)
downloadoauthlib-680459a1b9e42fab645138a587a218a77a8234d7.tar.gz
Add support for v01 MAC tokens
-rw-r--r--oauthlib/oauth2/draft25/__init__.py22
-rw-r--r--oauthlib/oauth2/draft25/tokens.py34
-rw-r--r--oauthlib/oauth2/draft25/utils.py9
-rw-r--r--tests/oauth2/draft25/test_client.py51
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):