Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Python 3 #56

Merged
merged 3 commits into from
Oct 23, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 66 additions & 34 deletions oauthlib/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals

"""
oauthlib.common
Expand All @@ -11,35 +11,67 @@

import random
import re
import string
import sys
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'
u'0123456789' u'_.-')


def quote(s, safe=u'/'):
encoded = s.encode("utf-8")
quoted = urllib.quote(encoded, safe)
return quoted.decode("utf-8")
try:
from urllib import quote as _quote
from urllib import unquote as _unquote
from urllib import urlencode as _urlencode
except ImportError:
from urllib.parse import quote as _quote
from urllib.parse import unquote as _unquote
from urllib.parse import urlencode as _urlencode
try:
import urlparse
except ImportError:
import urllib.parse as urlparse

UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'0123456789')

always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz'
'0123456789' '_.-')

PY3 = sys.version_info[0] == 3


if PY3:
unicode_type = str
bytes_type = bytes
else:
unicode_type = unicode
bytes_type = str


# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)
def quote(s, safe=b'/'):
s = _quote(s, safe)
# PY3 always returns unicode. PY2 may return either, depending on whether
# it had to modify the string.
if isinstance(s, bytes_type):
s = s.decode('utf-8')
return s


def unquote(s):
encoded = s.encode("utf-8")
unquoted = urllib.unquote(encoded)
return unquoted.decode("utf-8")
s = _unquote(s)
# PY3 always returns unicode. PY2 seems to always return what you give it,
# which differs from quote's behavior. Just to be safe, make sure it is
# unicode before we return.
if isinstance(s, bytes_type):
s = s.decode('utf-8')
return s


def urlencode(params):
utf8_params = encode_params_utf8(params)
urlencoded = urllib.urlencode(utf8_params)
return urlencoded.decode("utf-8")
urlencoded = _urlencode(utf8_params)
if isinstance(urlencoded, unicode_type): # PY3 returns unicode
return urlencoded
else:
return urlencoded.decode("utf-8")


def encode_params_utf8(params):
Expand All @@ -49,8 +81,8 @@ def encode_params_utf8(params):
encoded = []
for k, v in params:
encoded.append((
k.encode('utf-8') if isinstance(k, unicode) else k,
v.encode('utf-8') if isinstance(v, unicode) else v))
k.encode('utf-8') if isinstance(k, unicode_type) else k,
v.encode('utf-8') if isinstance(v, unicode_type) else v))
return encoded


Expand All @@ -61,12 +93,12 @@ def decode_params_utf8(params):
decoded = []
for k, v in params:
decoded.append((
k.decode('utf-8') if isinstance(k, str) else k,
v.decode('utf-8') if isinstance(v, str) else v))
k.decode('utf-8') if isinstance(k, bytes_type) else k,
v.decode('utf-8') if isinstance(v, bytes_type) else v))
return decoded


urlencoded = set(always_safe) | set(u'=&;%+~')
urlencoded = set(always_safe) | set('=&;%+~')


def urldecode(query):
Expand All @@ -86,11 +118,11 @@ def urldecode(query):
# All encoded values begin with % followed by two hex characters
# correct = %00, %A0, %0A, %FF
# invalid = %G0, %5H, %PO
invalid_hex = u'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]'
invalid_hex = '%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]'
if len(re.findall(invalid_hex, query)):
raise ValueError('Invalid hex encoding in query string.')

query = query.decode('utf-8') if isinstance(query, str) else query
query = query.decode('utf-8') if isinstance(query, bytes_type) else query
# We want to allow queries such as "c2" whereas urlparse.parse_qsl
# with the strict_parsing flag will not.
params = urlparse.parse_qsl(query, keep_blank_values=True)
Expand All @@ -107,7 +139,7 @@ def extract_params(raw):
empty list of parameters. Any other input will result in a return
value of None.
"""
if isinstance(raw, basestring):
if isinstance(raw, bytes_type) or isinstance(raw, unicode_type):
try:
params = urldecode(raw)
except ValueError:
Expand Down Expand Up @@ -140,7 +172,7 @@ def generate_nonce():
.. _`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())
return unicode_type(unicode_type(random.getrandbits(64)) + generate_timestamp())


def generate_timestamp():
Expand All @@ -152,7 +184,7 @@ def generate_timestamp():
.. _`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()))
return unicode_type(int(time.time()))


def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
Expand All @@ -164,7 +196,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
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))
return ''.join(rand.choice(chars) for x in range(length))


def add_params_to_qs(query, params):
Expand Down Expand Up @@ -214,7 +246,7 @@ class Request(object):
unmolested.
"""

def __init__(self, uri, http_method=u'GET', body=None, headers=None):
def __init__(self, uri, http_method='GET', body=None, headers=None):
self.uri = uri
self.http_method = http_method
self.headers = headers or {}
Expand Down
2 changes: 1 addition & 1 deletion oauthlib/oauth1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals

"""
oauthlib.oauth1
Expand Down
75 changes: 39 additions & 36 deletions oauthlib/oauth1/rfc5849/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals

"""
oauthlib.oauth1.rfc5849
Expand All @@ -11,22 +11,25 @@

import logging
import time
import urlparse
try:
import urlparse
except ImportError:
import urllib.parse as urlparse

from oauthlib.common import Request, urlencode, generate_nonce
from oauthlib.common import generate_timestamp
from . import parameters, signature, utils

SIGNATURE_HMAC = u"HMAC-SHA1"
SIGNATURE_RSA = u"RSA-SHA1"
SIGNATURE_PLAINTEXT = u"PLAINTEXT"
SIGNATURE_HMAC = "HMAC-SHA1"
SIGNATURE_RSA = "RSA-SHA1"
SIGNATURE_PLAINTEXT = "PLAINTEXT"
SIGNATURE_METHODS = (SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT)

SIGNATURE_TYPE_AUTH_HEADER = u'AUTH_HEADER'
SIGNATURE_TYPE_QUERY = u'QUERY'
SIGNATURE_TYPE_BODY = u'BODY'
SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER'
SIGNATURE_TYPE_QUERY = 'QUERY'
SIGNATURE_TYPE_BODY = 'BODY'

CONTENT_TYPE_FORM_URLENCODED = u'application/x-www-form-urlencoded'
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'


class Client(object):
Expand Down Expand Up @@ -94,18 +97,18 @@ def get_oauth_params(self):
"""Get the basic OAuth parameters to be used in generating a signature.
"""
params = [
(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),
('oauth_nonce', generate_nonce()),
('oauth_timestamp', generate_timestamp()),
('oauth_version', '1.0'),
('oauth_signature_method', self.signature_method),
('oauth_consumer_key', self.client_key),
]
if self.resource_owner_key:
params.append((u'oauth_token', self.resource_owner_key))
params.append(('oauth_token', self.resource_owner_key))
if self.callback_uri:
params.append((u'oauth_callback', self.callback_uri))
params.append(('oauth_callback', self.callback_uri))
if self.verifier:
params.append((u'oauth_verifier', self.verifier))
params.append(('oauth_verifier', self.verifier))

return params

Expand Down Expand Up @@ -135,15 +138,15 @@ def _render(self, request, formencode=False):
body = parameters.prepare_form_encoded_body(request.oauth_params, request.decoded_body)
if formencode:
body = urlencode(body)
headers['Content-Type'] = u'application/x-www-form-urlencoded'
headers['Content-Type'] = 'application/x-www-form-urlencoded'
elif self.signature_type == SIGNATURE_TYPE_QUERY:
uri = parameters.prepare_request_uri_query(request.oauth_params, request.uri)
else:
raise ValueError('Unknown signature type specified.')

return uri, headers, body

def sign(self, uri, http_method=u'GET', body=None, headers=None):
def sign(self, uri, http_method='GET', body=None, headers=None):
"""Sign a request

Signs an HTTP request with the specified parts.
Expand Down Expand Up @@ -208,7 +211,7 @@ def sign(self, uri, http_method=u'GET', body=None, headers=None):
request.oauth_params = self.get_oauth_params()

# generate the signature
request.oauth_params.append((u'oauth_signature', self.get_oauth_signature(request)))
request.oauth_params.append(('oauth_signature', self.get_oauth_signature(request)))

# render the signed request and return it
return self._render(request, formencode=True)
Expand Down Expand Up @@ -465,14 +468,14 @@ def get_signature_type_and_params(self, request):
params.extend(header_params)
params.extend(body_params)
params.extend(query_params)
signature_types_with_oauth_params = filter(lambda s: s[2], (
signature_types_with_oauth_params = list(filter(lambda s: s[2], (
(SIGNATURE_TYPE_AUTH_HEADER, params,
utils.filter_oauth_params(header_params)),
(SIGNATURE_TYPE_BODY, params,
utils.filter_oauth_params(body_params)),
(SIGNATURE_TYPE_QUERY, params,
utils.filter_oauth_params(query_params))
))
)))

if len(signature_types_with_oauth_params) > 1:
raise ValueError('oauth_ params must come from only 1 signature type but were found in %s' % ', '.join(
Expand Down Expand Up @@ -610,7 +613,7 @@ def validate_verifier(self, client_key, request_token, verifier):
"""
raise NotImplementedError("Subclasses must implement this function.")

def verify_request(self, uri, http_method=u'GET', body=None,
def verify_request(self, uri, http_method='GET', body=None,
headers=None, require_resource_owner=True, require_verifier=False,
require_realm=False, required_realm=None):
"""Verifies a request ensuring that the following is true:
Expand Down Expand Up @@ -644,11 +647,11 @@ def verify_request(self, uri, http_method=u'GET', body=None,
"""
# Only include body data from x-www-form-urlencoded requests
headers = headers or {}
if (u"Content-Type" in headers and
headers[u"Content-Type"] == CONTENT_TYPE_FORM_URLENCODED):
if ("Content-Type" in headers and
headers["Content-Type"] == CONTENT_TYPE_FORM_URLENCODED):
request = Request(uri, http_method, body, headers)
else:
request = Request(uri, http_method, u'', headers)
request = Request(uri, http_method, '', headers)

if self.enforce_ssl and not request.uri.lower().startswith("https://"):
raise ValueError("Insecure transport, only HTTPS is allowed.")
Expand All @@ -661,15 +664,15 @@ def verify_request(self, uri, http_method=u'GET', body=None,
raise ValueError("Duplicate OAuth entries.")

oauth_params = dict(oauth_params)
request_signature = oauth_params.get(u'oauth_signature')
client_key = oauth_params.get(u'oauth_consumer_key')
resource_owner_key = oauth_params.get(u'oauth_token')
nonce = oauth_params.get(u'oauth_nonce')
timestamp = oauth_params.get(u'oauth_timestamp')
callback_uri = oauth_params.get(u'oauth_callback')
verifier = oauth_params.get(u'oauth_verifier')
signature_method = oauth_params.get(u'oauth_signature_method')
realm = dict(params).get(u'realm')
request_signature = oauth_params.get('oauth_signature')
client_key = oauth_params.get('oauth_consumer_key')
resource_owner_key = oauth_params.get('oauth_token')
nonce = oauth_params.get('oauth_nonce')
timestamp = oauth_params.get('oauth_timestamp')
callback_uri = oauth_params.get('oauth_callback')
verifier = oauth_params.get('oauth_verifier')
signature_method = oauth_params.get('oauth_signature_method')
realm = dict(params).get('realm')

# The server SHOULD return a 400 (Bad Request) status code when
# receiving a request with missing parameters.
Expand All @@ -691,7 +694,7 @@ def verify_request(self, uri, http_method=u'GET', body=None,
# Servers receiving an authenticated request MUST validate it by:
# If the "oauth_version" parameter is present, ensuring its value is
# "1.0".
if u'oauth_version' in oauth_params and oauth_params[u'oauth_version'] != u'1.0':
if 'oauth_version' in oauth_params and oauth_params['oauth_version'] != '1.0':
raise ValueError("Invalid OAuth version.")

# The timestamp value MUST be a positive integer. Unless otherwise
Expand Down