summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIdan Gazit <idan@gazit.me>2012-04-11 03:04:26 -0700
committerIdan Gazit <idan@gazit.me>2012-04-11 03:04:26 -0700
commit03cb39d506d2698f2c3179eddc5a1a60d2b0589f (patch)
tree17676b119058a2324f86ecd63f3c2f3efa59680b
parent4c2f7f90b4c756b152cf9ff4fc7c413a2694e8cc (diff)
parent0144e370874e8525b955eb007a90c205e5df7559 (diff)
downloadoauthlib-03cb39d506d2698f2c3179eddc5a1a60d2b0589f.tar.gz
Merge pull request #16 from ib-lundgren/oauth2_utils
Utility methods for OAuth2
-rw-r--r--oauthlib/oauth2_draft25/utils.py128
-rw-r--r--tests/oauth2_draft25/test_utils.py57
2 files changed, 185 insertions, 0 deletions
diff --git a/oauthlib/oauth2_draft25/utils.py b/oauthlib/oauth2_draft25/utils.py
new file mode 100644
index 0000000..8705d6f
--- /dev/null
+++ b/oauthlib/oauth2_draft25/utils.py
@@ -0,0 +1,128 @@
+"""
+oauthlib.utils
+~~~~~~~~~~~~~~
+
+This module contains utility methods used by various parts of the OAuth 2 spec.
+"""
+
+import random
+import string
+import time
+import urllib
+from urlparse import urlparse, urlunparse, parse_qsl
+
+UNICODE_ASCII_CHARACTER_SET = (string.ascii_letters.decode('ascii') +
+ string.digits.decode('ascii'))
+
+def add_params_to_qs(query, params):
+ """Extend a query with a list of two-tuples.
+
+ :param query: Query string.
+ :param params: List of two-tuples.
+ :return: extended query
+ """
+ queryparams = parse_qsl(query, keep_blank_values=True)
+ queryparams.extend(params)
+ return urlencode(queryparams)
+
+
+def add_params_to_uri(uri, params):
+ """Add a list of two-tuples to the uri query components.
+
+ :param uri: Full URI.
+ :param params: List of two-tuples.
+ :return: uri with extended query
+ """
+ sch, net, path, par, query, fra = urlparse(uri)
+ query = add_params_to_qs(query, params)
+ return urlunparse((sch, net, path, par, query, fra))
+
+
+def escape(u):
+ """Escape a string in an OAuth-compatible fashion.
+
+ Per `section 3.6`_ of the spec.
+
+ .. _`section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
+
+ """
+ if not isinstance(u, unicode):
+ raise ValueError('Only unicode objects are escapable.')
+ return urllib.quote(u.encode('utf-8'), safe='~')
+
+
+def generate_nonce():
+ """Generate pseudorandom nonce that is unlikely to repeat.
+
+ Per `section 3.2.1`_ of the MAC Access Authentication spec.
+
+ A random 64-bit number is appended to the epoch timestamp for both
+ randomness and to decrease the likelihood of collisions.
+
+ .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
+ """
+ return unicode(unicode(random.getrandbits(64)) + generate_timestamp())
+
+
+def generate_timestamp():
+ """Get seconds since epoch (UTC).
+
+ Per `section 3.2.1`_ of the MAC Access Authentication spec.
+
+ .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
+ """
+ return unicode(int(time.time()))
+
+
+def generate_token(length=20, chars=UNICODE_ASCII_CHARACTER_SET):
+ """Generates a generic OAuth 2 token
+
+ According to `section 1.4`_ and `section 1.5` of the spec, the method of token
+ construction is undefined. This implementation is simply a random selection
+ of `length` choices from `chars`. SystemRandom is used since it provides
+ higher entropy than random.choice.
+
+ .. _`section 1.4`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-1.4
+ .. _`section 1.5`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-1.5
+ """
+ rand = random.SystemRandom()
+ return u''.join(rand.choice(chars) for x in range(length))
+
+
+def host_from_uri(uri):
+ """Extract hostname and port from URI.
+
+ Will use default port for HTTP and HTTPS if none is present in the URI.
+
+ >>> host_from_uri(u'https://www.example.com/path?query')
+ u'www.example.com', u'443'
+ >>> host_from_uri(u'http://www.example.com:8080/path?query')
+ u'www.example.com', u'8080'
+
+ :param uri: Full URI.
+ :param http_method: HTTP request method.
+ :return: hostname, port
+ """
+ default_ports = {
+ u'HTTP' : u'80',
+ u'HTTPS' : u'443',
+ }
+
+ sch, netloc, path, par, query, fra = urlparse(uri)
+ if u':' in netloc:
+ netloc, port = netloc.split(u':', 1)
+ else:
+ port = default_ports.get(sch.upper())
+
+ return netloc, port
+
+
+def urlencode(query):
+ """Encode a sequence of two-element tuples or dictionary into a URL query string.
+
+ Operates using an OAuth-safe escape() method, in contrast to urllib.urlenocde.
+ """
+ # Convert dictionaries to list of tuples
+ if isinstance(query, dict):
+ query = query.items()
+ return "&".join(['='.join([escape(k), escape(v)]) for k, v in query])
diff --git a/tests/oauth2_draft25/test_utils.py b/tests/oauth2_draft25/test_utils.py
new file mode 100644
index 0000000..6622ad4
--- /dev/null
+++ b/tests/oauth2_draft25/test_utils.py
@@ -0,0 +1,57 @@
+from __future__ import absolute_import
+
+from ..unittest import TestCase
+
+from oauthlib.oauth2_draft25.utils import *
+
+
+class UtilsTests(TestCase):
+
+ def test_escape(self):
+ """Assert that we are only escaping unicode"""
+ self.assertRaises(ValueError, escape, "I am a string type. Not a unicode type.")
+ self.assertEqual(escape(u"I am a unicode type."), u"I%20am%20a%20unicode%20type.")
+
+ def test_generate_timestamp(self):
+ """ TODO: Better test here """
+ timestamp = generate_timestamp()
+ self.assertTrue(isinstance(timestamp, unicode))
+ self.assertTrue(int(timestamp))
+ self.assertTrue(int(timestamp) > 1331672335) # is this increasing?
+
+ def test_generate_nonce(self):
+ """ TODO: better test here """
+ nonce = generate_nonce()
+ for i in range(50):
+ self.assertTrue(nonce != generate_nonce())
+
+ def test_generate_token(self):
+ """ TODO: better test here"""
+ token = generate_token()
+ self.assertEqual(len(token), 20)
+
+ token = generate_token(length=44)
+ self.assertEqual(len(token), 44)
+
+ token = generate_token(length=6, chars="python")
+ self.assertEqual(len(token), 6)
+ self.assertTrue("a" not in token)
+
+ def test_host_from_uri(self):
+ """Test if hosts and ports are properly extracted from URIs.
+
+ This should be done according to the MAC Authentication spec.
+ Defaults ports should be provided when none is present in the URI.
+ """
+ self.assertEqual(host_from_uri(u'http://a.b-c.com:8080'), (u'a.b-c.com', u'8080'))
+ self.assertEqual(host_from_uri(u'https://a.b.com:8080'), (u'a.b.com', u'8080'))
+ self.assertEqual(host_from_uri(u'http://www.example.com'), (u'www.example.com', u'80'))
+ self.assertEqual(host_from_uri(u'https://www.example.com'), (u'www.example.com', u'443'))
+
+
+
+ def test_urlencode(self):
+ """Ensure query components encoded properly"""
+ self.assertEqual(urlencode([(u'hello', u' world')]), u'hello=%20world')
+ self.assertEqual(urlencode({u'hello' : u' world'}), u'hello=%20world')
+