diff options
author | Ib Lundgren <ib.lundgren@gmail.com> | 2012-06-13 17:34:09 +0200 |
---|---|---|
committer | Ib Lundgren <ib.lundgren@gmail.com> | 2012-06-13 17:34:09 +0200 |
commit | 1edaaf2f5e92744e4fd06c908e507a47279b3db9 (patch) | |
tree | 0ad2594b925f3abc64765496652f3ff7db3ed5e3 | |
parent | 44ba3fd479aeb2eac28814054bb123a71204ed27 (diff) | |
download | oauthlib-1edaaf2f5e92744e4fd06c908e507a47279b3db9.tar.gz |
Move shared functionality (#30)
-rw-r--r-- | oauthlib/common.py | 58 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/__init__.py | 7 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/utils.py | 44 | ||||
-rw-r--r-- | oauthlib/oauth2/draft25/tokens.py | 5 | ||||
-rw-r--r-- | oauthlib/oauth2/draft25/utils.py | 113 | ||||
-rw-r--r-- | tests/oauth1/rfc5849/test_utils.py | 29 | ||||
-rw-r--r-- | tests/oauth2/draft25/test_utils.py | 33 | ||||
-rw-r--r-- | tests/test_common.py | 25 |
8 files changed, 104 insertions, 210 deletions
diff --git a/oauthlib/common.py b/oauthlib/common.py index 4cdfd0d..a2a30b3 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -9,10 +9,15 @@ This module provides data structures and utilities common to all implementations of OAuth. """ +import random import re +import string +import time import urllib import urlparse +UNICODE_ASCII_CHARACTER_SET = (string.ascii_letters.decode('ascii') + + string.digits.decode('ascii')) always_safe = (u'ABCDEFGHIJKLMNOPQRSTUVWXYZ' u'abcdefghijklmnopqrstuvwxyz' @@ -123,6 +128,59 @@ def extract_params(raw): return params +def generate_nonce(): + """Generate pseudorandom nonce that is unlikely to repeat. + + Per `section 3.3`_ of the OAuth 1 RFC 5849 spec. + 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 + .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + """ + return unicode(unicode(random.getrandbits(64)) + generate_timestamp()) + + +def generate_timestamp(): + """Get seconds since epoch (UTC). + + Per `section 3.3`_ of the OAuth 1 RFC 5849 spec. + 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 + .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + """ + return unicode(int(time.time())) + + +def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET): + """Generates a non-guessable OAuth token + + OAuth (1 and 2) does not specify the format of tokens except that they + should be strings of random characters. Tokens should not be guessable + and entropy when generating the random characters is important. Which is + why SystemRandom is used instead of the default random.choice method. + """ + rand = random.SystemRandom() + return u''.join(rand.choice(chars) for x in range(length)) + + +def add_params_to_qs(query, params): + """Extend a query with a list of two-tuples.""" + queryparams = urlparse.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.""" + sch, net, path, par, query, fra = urlparse.urlparse(uri) + query = add_params_to_qs(query, params) + return urlparse.urlunparse((sch, net, path, par, query, fra)) + + class Request(object): """A malleable representation of a signable HTTP request. diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index 03fb8b2..165a007 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -12,7 +12,8 @@ for signing and checking OAuth 1.0 RFC 5849 requests. import logging import urlparse -from oauthlib.common import Request, urlencode +from oauthlib.common import Request, urlencode, generate_nonce +from oauthlib.common import generate_timestamp from . import parameters, signature, utils SIGNATURE_HMAC = u"HMAC-SHA1" @@ -92,8 +93,8 @@ class Client(object): """Get the basic OAuth parameters to be used in generating a signature. """ params = [ - (u'oauth_nonce', utils.generate_nonce()), - (u'oauth_timestamp', utils.generate_timestamp()), + (u'oauth_nonce', generate_nonce()), + (u'oauth_timestamp', generate_timestamp()), (u'oauth_version', u'1.0'), (u'oauth_signature_method', self.signature_method), (u'oauth_consumer_key', self.client_key), diff --git a/oauthlib/oauth1/rfc5849/utils.py b/oauthlib/oauth1/rfc5849/utils.py index 6db446f..8fb0e77 100644 --- a/oauthlib/oauth1/rfc5849/utils.py +++ b/oauthlib/oauth1/rfc5849/utils.py @@ -9,9 +9,7 @@ spec. """ import string -import time import urllib2 -from random import getrandbits, choice from oauthlib.common import quote, unquote @@ -42,45 +40,6 @@ def filter_oauth_params(params): return filter(is_oauth, params) -def generate_timestamp(): - """Get seconds since epoch (UTC). - - Per `section 3.3`_ of the spec. - - .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 - """ - return unicode(int(time.time())) - - -def generate_nonce(): - """Generate pseudorandom nonce that is unlikely to repeat. - - Per `section 3.3`_ of the spec. - - A random 64-bit number is appended to the epoch timestamp for both - randomness and to decrease the likelihood of collisions. - - .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 - """ - return unicode(getrandbits(64)) + generate_timestamp() - - -def generate_token(length=20, chars=UNICODE_ASCII_CHARACTER_SET): - """Generates a generic OAuth token - - According to `section 2`_ of the spec, the method of token - construction is undefined. This implementation is simply a random selection - of `length` choices from `chars`. - - Credit to Ignacio Vazquez-Abrams for his excellent `Stackoverflow answer`_ - - .. _`Stackoverflow answer` : http://stackoverflow.com/questions/2257441/ - python-random-string-generation-with-upper-case-letters-and-digits - - """ - return u''.join(choice(chars) for x in range(length)) - - def escape(u): """Escape a unicode string in an OAuth-compatible fashion. @@ -118,7 +77,7 @@ def parse_keqv_list(l): encoded_list = [u.encode('utf-8') for u in l] encoded_parsed = urllib2.parse_keqv_list(encoded_list) return dict((k.decode('utf-8'), - v.decode('utf-8')) for k,v in encoded_parsed.items()) + v.decode('utf-8')) for k, v in encoded_parsed.items()) def parse_http_list(u): @@ -138,4 +97,3 @@ def parse_authorization_header(authorization_header): return parse_keqv_list(items).items() except ValueError: raise ValueError('Malformed authorization header') - diff --git a/oauthlib/oauth2/draft25/tokens.py b/oauthlib/oauth2/draft25/tokens.py index 9b5f586..74491fb 100644 --- a/oauthlib/oauth2/draft25/tokens.py +++ b/oauthlib/oauth2/draft25/tokens.py @@ -14,6 +14,7 @@ import hashlib import hmac from urlparse import urlparse +from oauthlib.common import add_params_to_uri, add_params_to_qs from . import utils @@ -105,7 +106,7 @@ def prepare_bearer_uri(token, uri): .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18 """ - return utils.add_params_to_uri(uri, [((u'access_token', token))]) + return add_params_to_uri(uri, [((u'access_token', token))]) def prepare_bearer_headers(token, headers=None): @@ -128,4 +129,4 @@ def prepare_bearer_body(token, body=u''): .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18 """ - return utils.add_params_to_qs(body, [((u'access_token', token))]) + return add_params_to_qs(body, [((u'access_token', token))]) diff --git a/oauthlib/oauth2/draft25/utils.py b/oauthlib/oauth2/draft25/utils.py index 48b4ea1..75d5fcc 100644 --- a/oauthlib/oauth2/draft25/utils.py +++ b/oauthlib/oauth2/draft25/utils.py @@ -5,110 +5,21 @@ 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)) +import urlparse 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 + Will use default port for HTTP and HTTPS if none is present in the URI. """ default_ports = { - u'HTTP' : u'80', - u'HTTPS' : u'443', + u'HTTP': u'80', + u'HTTPS': u'443', } - sch, netloc, path, par, query, fra = urlparse(uri) + sch, netloc, path, par, query, fra = urlparse.urlparse(uri) if u':' in netloc: netloc, port = netloc.split(u':', 1) else: @@ -117,12 +28,12 @@ def host_from_uri(uri): return netloc, port -def urlencode(query): - """Encode a sequence of two-element tuples or dictionary into a URL query string. +def escape(u): + """Escape a string in an OAuth-compatible fashion. + + TODO: verify whether this can in fact be used for OAuth 2 - 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]) + if not isinstance(u, unicode): + raise ValueError('Only unicode objects are escapable.') + return urllib.quote(u.encode('utf-8'), safe='~') diff --git a/tests/oauth1/rfc5849/test_utils.py b/tests/oauth1/rfc5849/test_utils.py index 8abdb38..54abf9d 100644 --- a/tests/oauth1/rfc5849/test_utils.py +++ b/tests/oauth1/rfc5849/test_utils.py @@ -90,31 +90,6 @@ class UtilsTests(TestCase): self.assertTrue(filtered_params[0][0].startswith('oauth')) self.assertTrue(filtered_params[1][0].startswith('oauth')) - def test_generate_timestamp(self): - """ TODO: Better test here """ - timestamp = generate_timestamp() - self.assertIsInstance(timestamp, unicode) - self.assertTrue(int(timestamp)) - self.assertGreater(int(timestamp), 1331672335) # is this increasing? - - def test_generate_nonce(self): - """ TODO: better test here """ - - nonce = generate_nonce() - for i in range(50): - self.assertNotEqual(nonce, generate_nonce()) - - def test_generate_token(self): - 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.assertNotIn("a", token) - def test_escape(self): 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.") @@ -125,10 +100,6 @@ class UtilsTests(TestCase): self.assertEqual(unescape(u"I%20am%20a%20unicode%20type."), u'I am a unicode type.') self.assertIsInstance(unescape(u"I%20am%20a%20unicode%20type."), unicode) - def test_urlencode(self): - self.assertEqual(urlencode(self.sample_params_unicode_list), "notoauth=shouldnotbehere&oauth_consumer_key=9djdj82h48djs9d2&oauth_token=kkk9d7dh3k39sjv7¬oautheither=shouldnotbehere") - self.assertEqual(urlencode(self.sample_params_unicode_dict), "notoauth=shouldnotbehere&oauth_consumer_key=9djdj82h48djs9d2&oauth_token=kkk9d7dh3k39sjv7¬oautheither=shouldnotbehere") - def test_parse_authorization_header(self): # make us some headers authorization_headers = parse_authorization_header(self.authorization_header) diff --git a/tests/oauth2/draft25/test_utils.py b/tests/oauth2/draft25/test_utils.py index 3844392..c526891 100644 --- a/tests/oauth2/draft25/test_utils.py +++ b/tests/oauth2/draft25/test_utils.py @@ -1,8 +1,7 @@ from __future__ import absolute_import from ...unittest import TestCase - -from oauthlib.oauth2.draft25.utils import * +from oauthlib.oauth2.draft25.utils import escape, host_from_uri class UtilsTests(TestCase): @@ -12,31 +11,6 @@ class UtilsTests(TestCase): 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. @@ -47,8 +21,3 @@ class UtilsTests(TestCase): 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') diff --git a/tests/test_common.py b/tests/test_common.py index 457b96d..85ceedd 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -80,3 +80,28 @@ class CommonTests(TestCase): def test_dict_body(self): r = Request(self.uri, body=self.params_dict) self.assertEqual(r.decoded_body, self.params_twotuple) + + def test_generate_timestamp(self): + """ TODO: Better test here """ + timestamp = generate_timestamp() + self.assertIsInstance(timestamp, unicode) + self.assertTrue(int(timestamp)) + self.assertGreater(int(timestamp), 1331672335) # is this increasing? + + def test_generate_nonce(self): + """ TODO: better test here """ + + nonce = generate_nonce() + for i in range(50): + self.assertNotEqual(nonce, generate_nonce()) + + def test_generate_token(self): + token = generate_token() + self.assertEqual(len(token), 30) + + token = generate_token(length=44) + self.assertEqual(len(token), 44) + + token = generate_token(length=6, chars="python") + self.assertEqual(len(token), 6) + self.assertNotIn("a", token) |